Help us understand the problem. What is going on with this article?

Genie DiffによるCisco機器のConfig/showコマンド比較

More than 1 year has passed since last update.

はじめに

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メソッドで構造化しています。

genie9-1.py
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) 結果を比較

genie9-2.py
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. 実行結果④enabledlast_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を実行しています。

genie9-3.py
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ユーザーの方にとっても非常に有用な機能だと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away