はじめに
Ciscoが公開しているPythonベースのテストフレームワーク/フィーチャーライブラリー「pyATS/Genie」の内、Genie Diffを使ってみた結果をメモしておきます。
Genie Diffとは
2つの辞書データもしくはオブジェクトを比較し、差分(追加・削除・修正)を出力してくれるPythonオブジェクトです。
Configやshowコマンドの結果をそのまま比較するのではなく、例えばConfigはGenie Config、showコマンドはGenie Parserを使って辞書形式の構造化データに変換したり、Genie ConfやGenie Opsのオブジェクト形式にしたりする必要があります。
参考URL: Genie Utils - Diff
以下の1.でConfig、2.でshowコマンドの比較例をご紹介します。
1. Configの比較
1-1. Configデータの構造化
Python環境のセットアップはこちらの記事を参照願います。(Testbedも用意する必要あり。)
今回はCisco CSR1000Vのshow runを取得し、辞書形式の構造化データに変換します。
1-2. Pythonコード①
executeメソッドでshow runを実行した後、Configオブジェクトからconfigインスタンスを生成し、treeメソッドで構造化しています。
from genie.conf import Genie
from genie.utils.config import Config
from pprint import pprint
testbed = Genie.init('testbed_devnet3.yaml')
device = testbed.devices['csr1000v-1']
device.connect()
# show runを実行
cfg = device.execute('show run')
print(cfg)
# show runの結果を辞書形式の構造化データに変換
config = Config(cfg)
config.tree()
pprint(config.config)
1-3. 実行結果①
以下は構造化前のshow run結果の抜粋です。
$ python genie9-1.py
# 途中の実行ログは省略
# print(cfg)の結果抜粋
Building configuration...
Current configuration : 6818 bytes
!
! Last configuration change at 04:47:24 UTC Sat Jul 13 2019 by developer
!
version 16.11
service timestamps debug datetime msec
service timestamps log datetime msec
(中略)
interface GigabitEthernet1
description MANAGEMENT INTERFACE - DON'T TOUCH ME
ip address 10.10.20.48 255.255.255.0
negotiation auto
no mop enabled
no mop sysid
!
interface GigabitEthernet2
description Network Interface
no ip address
shutdown
negotiation auto
no mop enabled
no mop sysid
!
interface GigabitEthernet3
description Network Interface
no ip address
shutdown
negotiation auto
no mop enabled
no mop sysid
!
(中略)
以下は構造化後のデータです。上記に対応する部分のみ抜き出しています。
階層構造を持つinterfaceコマンドは、ネストの辞書データに変換されています。また、全体的に順番が大文字A-Z⇒小文字A-Zに変更されています。元データ内の「! Last configuration ~」は、構造化データでは除外されていました。
# pprint(config.config)の結果抜粋
{'Building configuration...': {},
'Current configuration : 6818 bytes': {},
'interface GigabitEthernet1': {"description MANAGEMENT INTERFACE - DON'T TOUCH ME": {},
'ip address 10.10.20.48 255.255.255.0': {},
'negotiation auto': {},
'no mop enabled': {},
'no mop sysid': {}},
'interface GigabitEthernet2': {'description Network Interface': {},
'negotiation auto': {},
'no ip address': {},
'no mop enabled': {},
'no mop sysid': {},
'shutdown': {}},
'interface GigabitEthernet3': {'description Network Interface': {},
'negotiation auto': {},
'no ip address': {},
'no mop enabled': {},
'no mop sysid': {},
'shutdown': {}},
'service timestamps debug datetime msec': {},
'service timestamps log datetime msec': {},
'version 16.11': {}}
1-4. 構造化データの差分比較
今回は、新規IFの設定を行い、その前後でConfig比較をしてみます。
1-5. Pythonコード②
大まかな流れは以下の通りです。
(1) 事前の構造化データを取得
(2) 新規IF設定
(3) 事後の構造化データを取得
(4) 結果を比較
from genie.conf import Genie
from genie.utils.config import Config
from genie.conf.base import Interface
from genie.utils.diff import Diff
testbed = Genie.init('testbed_devnet3.yaml')
device = testbed.devices['csr1000v-1']
device.connect()
# (1) 事前の構造化データを取得
cfg1 = device.execute('show run')
config1 = Config(cfg1)
config1.tree()
cfg_dict1 = config1.config
# (2) 新規IF設定
# create an interface object
intf = Interface(device=device, name='GigabitEthernet2')
# configuration the interface by setting various object level attributes
intf.description = '< For Test >'
intf.ipv4 = '10.1.1.1'
intf.ipv4.netmask = '255.255.255.0'
intf.shutdown = False
# apply configuration on device
intf.build_config()
# (3) 事後の構造化データを取得
cfg2 = device.execute('show run')
config2 = Config(cfg2)
config2.tree()
cfg_dict2 = config2.config
# (4) 結果を比較
dd = Diff(cfg_dict1, cfg_dict2)
dd.findDiff()
print(dd)
1-6. 実行結果②
+は2つ目のConfigで追加された設定、-は削除された設定です。
interface GigabitEthernet2:には何も付いていませんが、これは自身ではなく配下の設定で差異があったためです。
+Current configuration : 6793 bytes:
-Current configuration : 6757 bytes:
interface GigabitEthernet2:
+ description < For Test >:
+ ip address 10.1.1.1 255.255.255.0:
- no ip address:
- shutdown:
1-7. Pythonコード③(差分の一部のみ表示)
コード②ではすべての差分をまとめて出力しましたが、追加(add)、削除(remove)、修正(modified)を分けて出力したり、動的設定などを比較対象から除外したりすることも可能です。
(1) 追加だけ表示する場合
Diffオブジェクトの引数に、mode='add'を追加します。
dd = Diff(cfg_dict1, cfg_dict2, mode='add')
(2) 削除だけ表示する場合
Diffオブジェクトの引数に、mode='remove'を追加します。
dd = Diff(cfg_dict1, cfg_dict2, mode='remove')
(3) 修正だけ表示する場合
Diffオブジェクトの引数に、mode='modified'を追加します。
dd = Diff(cfg_dict1, cfg_dict2, mode='modified')
(4) 比較対象から除外する場合
除外対象のコマンドをリスト形式の変数exclude_listに格納し、Diffオブジェクトの引数で、exclude=exclude_listを指定します。
exclude_list = ['(^Using.*)', '(Building.*)', '(Current.*)', '(crypto pki certificate chain.*)']
dd = Diff(cfg_dict1, cfg_dict2, exclude=exclude_list)
1-8. 実行結果③(差分の一部のみ表示)
(1) 追加だけ表示する場合
+Current configuration : 6793 bytes:
interface GigabitEthernet2:
+ description < For Test >:
+ ip address 10.1.1.1 255.255.255.0:
(2) 削除だけ表示する場合
-Current configuration : 6757 bytes:
interface GigabitEthernet2:
- no ip address:
- shutdown:
(3) 修正だけ表示する場合
今回は何も表示されません。修正の扱いになるのは、2-3. 実行結果④のenabledやlast_inputのように、key: valueの内、keyは変わらずvalueのみ変わっているものです。
(4) 比較対象から除外する場合
「Current configuration : ~」が除外されている事が分かります。
interface GigabitEthernet2:
+ description < For Test >:
+ ip address 10.1.1.1 255.255.255.0:
- no ip address:
- shutdown:
2. showコマンドの比較
2-1. showコマンドデータの構造化
showコマンド結果を辞書形式の構造化データに変換する方法は、以下記事の通りです。
Genie Parser/OpsによるCisco機器のログ取得・構文解析
2-2. Pythonコード④
大まかな流れはコード②と同じです。事前事後で、show runの代わりにshow interfaces GigabitEthernet2を実行しています。
from genie.conf import Genie
from genie.utils.config import Config
from genie.conf.base import Interface
from genie.utils.diff import Diff
testbed = Genie.init('testbed_devnet3.yaml')
device = testbed.devices['csr1000v-1']
device.connect()
# (1) 事前の構造化データを取得
output1 = device.parse('show interfaces GigabitEthernet2')
# (2) 新規IF設定
# create an interface object
intf = Interface(device=device, name='GigabitEthernet2')
# configuration the interface by setting various object level attributes
intf.description = '< For Test >'
intf.ipv4 = '10.1.1.1'
intf.ipv4.netmask = '255.255.255.0'
intf.shutdown = False
# apply configuration on device
intf.build_config()
# (3) 事後の構造化データを取得
output2 = device.parse('show interfaces GigabitEthernet2')
# (4) 結果を比較
dd = Diff(output1, output2)
dd.findDiff()
print(dd)
2-3. 実行結果④
各種ステータスを差分比較出来ている事が分かります。
GigabitEthernet2:
counters:
+ out_interface_resets: 16
- out_interface_resets: 14
+ enabled: True
- enabled: False
+ last_input: 00:13:40
- last_input: 00:13:35
+ last_output: 00:04:36
- last_output: 00:04:31
+ description: < For Test >
+ ipv4:
+ 10.1.1.1/24:
+ ip: 10.1.1.1
+ prefix_length: 24
補足
Genie Diffは、Ansibleのフィルタープラグインにも組み込まれています。(CiscoDevNetから公開されているもので、正式版にはマージされていません。)
GitHub - ansible-pyats
interfaceコマンドのような階層構造を理解して差分比較できる方法は、おそらく既存のAnsibleには存在しないため、Ansibleユーザーの方にとっても非常に有用な機能だと思います。