最近わたしの記事はpyATSばっかり
pyATSってなあに
Ciscoが開発している、検証用のPythonライブラリです。
細かいところは端折らせてください、、
詳しくは↓
公式ドキュメント
私の紹介記事
最近のQiitaで特に自動化界隈で何かと話題(と私は思っている)
作成したもの
何を作成したかというと、用意されたConfigを対象の機器に正しく投入して、
投入された結果がshow running-configで差分として表示され、さらにgenie learnで取得した構造化された出力をDiffして差分をわかりやすく表示すると言った内容になります。
今回は開始前のConfigに戻すような作りにしてあります。
├── check_acl.py ←スクリプトの本体
├── testbed
│ └── devnet_always.yml ←testbedふぁいる
└── vars
├── csr1000v-1.yml ←testbedに記載しているNodeのVars
├── iosxr1.yml
└── sbx-n9kv-ao.yml
testbed
接続先の情報がまとまっているファイル、今回は下記のものを用意
ちなみにこちらの説明は前回の記事出させていただいているので割愛させてください。
---
testbed:
name: SandBox
devices:
sbx-n9kv-ao:
alias: uut
credentials:
default:
username: admin
password: Admin_1234!
connections:
vty:
protocol: ssh
ip: sbx-nxos-mgmt.cisco.com
port: 8181
os: nxos
type: nxos
iosxr1:
alias: rt
credentials:
default:
username: admin
password: C1sco12345
connections:
vty:
protocol: ssh
ip: sbx-iosxr-mgmt.cisco.com
port: 8181
os: iosxr
type: iosxr
csr1000v-1:
alias: xe1
credentials:
default:
username: developer
password: C1sco12345
connections:
vty:
protocol: ssh
ip: ios-xe-mgmt-latest.cisco.com
port: 8181
os: iosxe
type: iosxe
csr1000v:
alias: xe0
credentials:
default:
username: developer
password: C1sco12345
connections:
vty:
protocol: ssh
ip: ios-xe-mgmt.cisco.com
port: 8181
os: iosxe
type: iosxe
Varsファイル(機器ごとに用意)
中身を説明するとcommand
には設定投入に必要なConfig、clear
には設定戻しに必要なConfigを記入しています。
IOSXEのcsr1000v-1にはStaticRouteとACLを追加
---
command: |
ip route 8.8.8.8 255.255.255.255 GigabitEthernet1 10.10.20.254
ip access-list extended TEST
permit ip any 8.8.8.8 0.0.0.0 log
remark ****TEST****
clear: |
no ip route 8.8.8.8 255.255.255.255 GigabitEthernet1 10.10.20.254
no ip access-list extended TEST
IOSXRのiosxr1はファイルのみ用意して、中身は空っぽです。
---
command: |
clear: |
Testbedにあるcsr1000vのVarsはあえて用意していません。
続いて自作したScriptの紹介をしますPythonで書いてあります、
醜い、イケテナイ!と感じた方はぜひ教えてください、業務で使っているわけでもなく検証で使っているので
バリバリのプログラマの方々にはへっぽこコードと感じるかもしれませんが、ご容赦ください。
Script
import logging
import yaml
#Genieモジュールをインポート
from genie.conf import Genie
from genie.utils.diff import Diff
class setupDevices(object):
def __init__(self):
#Device格納用
self.listDevice = list()
#running-config格納用
self.beforeConfig = list()
self.afterConfig = list()
#varsファイルがあるDeviceのみ格納
self.targetDevice = list()
self.config = dict()
#routing格納用
self.beforeRouting = list()
self.afterRouting = list()
#acl格納用
self.beforeAcl = list()
self.afterAcl = list()
#テストベッドを初期化
try:
self.testbed = Genie.init('testbed/devnet_always.yml')
if self.testbed :
logging.info('testbed init:OK')
for keys in self.testbed.devices:
self.listDevice.append(keys)
except TypeError:
logging.warning("Testbedファイルがおかしい")
#Varファイルがあるか確認
for device in self.listDevice:
try:
with open('vars/{}.yml'.format(device), 'r') as yml:
device_config = { device + "_config" :yaml.safe_load(yml)}
self.config.update(device_config)
self.targetDevice.append(device)
except FileNotFoundError:
logging.warning("{}のファイルない".format(device))
continue
#loginの処理に入る
self.login()
#ログイン
def login(self):
logging.info("*"*32 + "ログイン" + "*"*32)
for device in self.targetDevice:
self.testbed.devices[device].connect(log_stdout=False)
if self.testbed.devices[device].is_connected():
logging.info("{}: connected".format(device))
else:
logging.info("{}: disconnected".format(device))
# 事前ログ取得
def getBeforeAcl(self):
logging.info("*"*32 + "事前ACL確認" + "*"*32)
for device in self.targetDevice:
self.beforeAcl.append(self.testbed.devices[device].learn('acl'))
logging.info("{}: beforeAcl get".format(device))
def getBeforeConfig(self):
logging.info("*"*32 + "事前ログ取得"+ "*"*32)
for device in self.targetDevice:
self.beforeConfig.append(self.testbed.devices[device].learn('config'))
logging.info("{}: beforeConfig get".format(device))
def getBeforeRoute(self):
logging.info("*"*32 + "事前RoutingTabel確認" + "*"*32)
for device in self.targetDevice:
self.beforeRouting.append(self.testbed.devices[device].learn('routing'))
logging.info("{}: beforeRouting get".format(device))
#Config投入
def inputConfig(self):
logging.info("*"*32 + "Config投入" + "*"*32)
for device in self.targetDevice:
self.testbed.devices[device].configure(self.config[device+"_config"]['command'])
logging.info("{}: input_config".format(device))
#投入したConfig削除
def claerConfig(self):
logging.info("*"*32 + "Config戻し" + "*"*32)
for device in self.targetDevice:
self.testbed.devices[device].configure(self.config[device+"_config"]['clear'])
logging.info("{}: clear_config".format(device))
#事後Log取得
def getAfterConfig(self):
logging.info("*"*32 + "事後ログ取得" + "*"*32)
for device in self.targetDevice:
self.afterConfig.append(self.testbed.devices[device].learn('config'))
logging.info("{}: afterConfig get".format(device))
def getAfterAcl(self):
logging.info("*"*32 + "事後ACL確認" + "*"*32)
for device in self.targetDevice:
self.afterAcl.append(self.testbed.devices[device].learn('acl'))
logging.info("{}: afterAcl get".format(device))
def getAfterRoute(self):
logging.info("*"*32 + "事後RoutingTabel確認" + "*"*32)
for device in self.targetDevice:
self.afterRouting.append(self.testbed.devices[device].learn('routing'))
logging.info("{}: afterRouting get".format(device))
#ログアウト
def logout(self):
logging.info("*"*32 + "ログアウト" + "*"*32)
for device in self.targetDevice:
self.testbed.devices[device].disconnect()
if self.testbed.devices[device].is_connected():
logging.info("{}: connected".format(device))
else:
logging.info("{}: disconnected".format(device))
#ConfigをDiff
def diffConfig(self):
logging.info("*"*32 + "Diff" + "*"*32)
if self.beforeConfig and self.afterConfig:
for config_b,config_a,device in zip(self.beforeConfig,self.afterConfig,self.self.targetDevice):
config_diff = Diff(config_b,config_a)
config_diff.findDiff()
print("\n====={}_ConfigDiff=====".format(device))
print(config_diff)
if self.beforeRouting and self.afterRouting:
for config_b,config_a,device in zip(self.beforeRouting,self.afterRouting,self.self.targetDevice):
config_diff = Diff(config_b,config_a)
config_diff.findDiff()
print("\n====={}_RouteDiff=====".format(device))
print(config_diff)
if self.beforeAcl and self.afterAcl:
for config_b,config_a,device in zip(self.beforeAcl,self.afterAcl,self.self.targetDevice):
config_diff = Diff(config_b,config_a)
config_diff.findDiff()
print("\n====={}_AclDiff=====".format(device))
print(config_diff)
if __name__ == "__main__":
#ログレベルを info に変更
logging.basicConfig(level=logging.INFO)
logging.info("*"*32 + "処理開始" + "*"*32)
devnet = setupDevices()
devnet.getBeforeConfig()
devnet.getBeforeRoute()
devnet.getBeforeAcl()
devnet.inputConfig()
devnet.getAfterConfig()
devnet.getAfterRoute()
devnet.getAfterAcl()
devnet.claerConfig()
devnet.logout()
devnet.diffConfig()
logging.info("*"*32 + "終了" + "*"*32)
実行結果の説明
一気に貼るとLogがぼうだいなので、小分けにして説明します
python check_acl.py
でスクリプト実行します。
起動時の処理として、testbedファイルを読み込みます。
続いて、testbedにあるDevice名+_config.ymlファイルを読み込みます。
この時、対象のyamlファイルがない場合、「〇〇〇のファイルがない」とメッセージを出しています。
(nw_automate) 00:26:33 my Script $python check_acl.py
INFO : ********************************処理開始********************************
INFO : testbed init:OK
WARNING : sbx-n9kv-aoのファイルない
WARNING : csr1000vのファイルない
続いてログインの処理ですが、
devices[device].connect(log_stdout=False)
でログインをします。
この時、log_stdout=False
を指定しているので、ログイン時のログが表示されません。
INFO : ********************************ログイン********************************
INFO : iosxr1: connected
INFO : csr1000v-1: connected
ログインが完了したら、各種事前ログを取得します。
取得後に各種事前の状態をListに格納していきます。
確認したい項目 | 取得できるOps |
---|---|
RunningConfig | device['デバイス名'].learn('config') |
ACL | device['デバイス名'].learn('acl') |
Routing | device['デバイス名'].learn('routing') |
※ログが大量に出ましたのでいい具合に省略しています。
INFO : ********************************事前ログ取得********************************
INFO : iosxr1: beforeConfig get
INFO : csr1000v-1: beforeConfig get
INFO : ********************************事前RoutingTabel確認********************************
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_routing.ShowRouteIpv6Distributor'> 'genie.libs.parser.iosxr.show_routing.ShowRouteIpDistributor'> 'genie.libs.parser.iosxr.show_routing.ShowRouteIpv6Distributor'>
INFO : iosxr1: beforeRouting get
INFO : Could not learn <class 'genie.libs.parser.iosxe.show_routing.ShowIpv6RouteDistributor'>
'genie.libs.parser.iosxe.show_routing.ShowIpRouteDistributor'> 'genie.libs.parser.iosxe.show_routing.ShowIpv6RouteDistributor'>
INFO : csr1000v-1: beforeRouting get
INFO : ********************************事前ACL確認********************************
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_acl.ShowAclAfiAll'>
Parser Output is empty
INFO : Could not learn <class'genie.libs.parser.iosxr.show_acl.ShowAclEthernetServices'>
INFO : | Commands for learning feature 'Acl'
INFO : | - Commands with empty output
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_acl.ShowAclAfiAll'>
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_acl.ShowAclEthernetServices'>
INFO : iosxr1: beforeAcl get
INFO : | Commands for learning feature 'Acl'
INFO : | cmd: <class 'genie.libs.parser.iosxe.show_acl.ShowAccessLists'>
INFO : csr1000v-1: beforeAcl get
続いて、Varsファイルの[command]
に記載されているConfigを投入します。
投入後に事後ログとして事前に取ったログと同様のものを取得します。(省略・・・)
INFO : ********************************Config投入********************************
INFO : iosxr1: input_config
INFO : csr1000v-1: input_config
INFO : ********************************事後ログ取得********************************
INFO : iosxr1: afterConfig get
INFO : csr1000v-1: afterConfig get
INFO : ********************************事後RoutingTabel確認********************************
事後ログを取得後に、Configも元に戻します。
varsファイルのclear
に記載されている内容で戻します。
※今気付いたんですが、戻しを実施した後、本当に戻ったのか?を確認するための処理がなかったですね。。
その後、ログアウトを実施します。
ログアウト後に現在のコネクションステータスを確認して状態を表示させています。
INFO : ********************************Config戻し********************************
INFO : iosxr1: clear_config
INFO : csr1000v-1: clear_config
INFO : ********************************ログアウト********************************
INFO : iosxr1: disconnected
INFO : csr1000v-1: disconnected
最後に、取得した事前Confgiと事後ConfigをDiffします。
文の先頭に+かーの符号がついていますが、
+が追加で
ーが削除です。
例えば、iosxr1のConfigDiffでは、階層をいい具合に表示してくれます。
これにより、単純にTextを比較するよりかなりわかりやすいのではないでしょうか?
INFO : ********************************Diff********************************
=====iosxr1_ConfigDiff=====
+Wed Dec 18 16:57:14.949 UTC:
-Wed Dec 18 16:56:58.216 UTC:
router static:
address-family ipv4 unicast:
+ 8.8.8.8/32 10.10.20.254:
=====csr1000v-1_ConfigDiff=====
+Current configuration : 7620 bytes:
+ip route 8.8.8.8 255.255.255.255 GigabitEthernet1 10.10.20.254:
-Current configuration : 7557 bytes:
=====iosxr1_RouteDiff=====
info:
vrf:
default:
address_family:
ipv4:
routes:
+ 8.8.8.8/32:
+ active: True
+ metric: 0
+ next_hop:
+ next_hop_list:
+ 1:
+ index: 1
+ next_hop: 10.10.20.254
+ updated: 00:00:08
+ route: 8.8.8.8/32
+ route_preference: 1
+ source_protocol: static
+ source_protocol_codes: S
=====csr1000v-1_RouteDiff=====
info:
vrf:
default:
address_family:
ipv4:
routes:
+ 8.8.8.8/32:
+ active: True
+ metric: 0
+ next_hop:
+ next_hop_list:
+ 1:
+ index: 1
+ next_hop: 10.10.20.254
+ outgoing_interface: GigabitEthernet1
+ route: 8.8.8.8/32
+ route_preference: 1
+ source_protocol: static
+ source_protocol_codes: S
=====iosxr1_AclDiff=====
=====csr1000v-1_AclDiff=====
INFO : ********************************終了********************************
まとめ
コードばかりで、読みづらい記事になってしまって申し訳ありませんでした、
ただ、Diffの結果をご覧いただいた通り、確認は簡単にすることができました。
Pythonを一生懸命書きましたが、実はコードを書かずとも今回と同じようなことができます。
参考になるURLを貼っておきます。
pyATS|Genieの隠れ必殺技Blitzを用いて高速自動化テスト作成
[pyATS/GenieとRobot Framework で簡単にマルチベンダーネットワークをチェックする]
(https://qiita.com/tahigash/items/ceb0809a4a9464c521b3)
今回のコードは0から書いたわけではなく、ネットに落ちている情報を拾い集めてなんとか形にしました。
[CiscoTestAutomation](https://github.com/CiscoTestAutomationというGithubに参考になるコードがちょくちょくあります。
おまけ
今回実施した、Log全部
(nw_automate) 00:26:33 my Script $python check_acl.py
INFO : ********************************処理開始********************************
INFO : testbed init:OK
WARNING : sbx-n9kv-aoのファイルない
WARNING : csr1000vのファイルない
INFO : ********************************ログイン********************************
INFO : iosxr1: connected
INFO : csr1000v-1: connected
INFO : ********************************事前ログ取得********************************
INFO : iosxr1: beforeConfig get
INFO : csr1000v-1: beforeConfig get
INFO : ********************************事前RoutingTabel確認********************************
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_routing.ShowRouteIpv6Distributor'>
Parser Output is empty
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Routing' |
INFO : +====================================================================================================================================================+
INFO : | - Parsed commands |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_routing.ShowRouteIpDistributor'> |
INFO : |====================================================================================================================================================|
INFO : | - Commands with empty output |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_routing.ShowRouteIpv6Distributor'> |
INFO : |====================================================================================================================================================|
INFO : iosxr1: beforeRouting get
INFO : Could not learn <class 'genie.libs.parser.iosxe.show_routing.ShowIpv6RouteDistributor'>
Parser Output is empty
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Routing' |
INFO : +====================================================================================================================================================+
INFO : | - Parsed commands |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxe.show_routing.ShowIpRouteDistributor'> |
INFO : |====================================================================================================================================================|
INFO : | - Commands with empty output |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxe.show_routing.ShowIpv6RouteDistributor'> |
INFO : |====================================================================================================================================================|
INFO : csr1000v-1: beforeRouting get
INFO : ********************************事前ACL確認********************************
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_acl.ShowAclAfiAll'>
Parser Output is empty
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_acl.ShowAclEthernetServices'>
Parser Output is empty
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Acl' |
INFO : +====================================================================================================================================================+
INFO : | - Commands with empty output |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_acl.ShowAclAfiAll'> |
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_acl.ShowAclEthernetServices'> |
INFO : |====================================================================================================================================================|
INFO : iosxr1: beforeAcl get
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Acl' |
INFO : +====================================================================================================================================================+
INFO : | - Parsed commands |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxe.show_acl.ShowAccessLists'> |
INFO : |====================================================================================================================================================|
INFO : csr1000v-1: beforeAcl get
INFO : ********************************Config投入********************************
INFO : iosxr1: input_config
INFO : csr1000v-1: input_config
INFO : ********************************事後ログ取得********************************
INFO : iosxr1: afterConfig get
INFO : csr1000v-1: afterConfig get
INFO : ********************************事後RoutingTabel確認********************************
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_routing.ShowRouteIpv6Distributor'>
Parser Output is empty
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Routing' |
INFO : +====================================================================================================================================================+
INFO : | - Parsed commands |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_routing.ShowRouteIpDistributor'> |
INFO : |====================================================================================================================================================|
INFO : | - Commands with empty output |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_routing.ShowRouteIpv6Distributor'> |
INFO : |====================================================================================================================================================|
INFO : iosxr1: afterRouting get
INFO : Could not learn <class 'genie.libs.parser.iosxe.show_routing.ShowIpv6RouteDistributor'>
Parser Output is empty
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Routing' |
INFO : +====================================================================================================================================================+
INFO : | - Parsed commands |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxe.show_routing.ShowIpRouteDistributor'> |
INFO : |====================================================================================================================================================|
INFO : | - Commands with empty output |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxe.show_routing.ShowIpv6RouteDistributor'> |
INFO : |====================================================================================================================================================|
INFO : csr1000v-1: afterRouting get
INFO : ********************************事後ACL確認********************************
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_acl.ShowAclAfiAll'>
Parser Output is empty
INFO : Could not learn <class 'genie.libs.parser.iosxr.show_acl.ShowAclEthernetServices'>
Parser Output is empty
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Acl' |
INFO : +====================================================================================================================================================+
INFO : | - Commands with empty output |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_acl.ShowAclAfiAll'> |
INFO : | cmd: <class 'genie.libs.parser.iosxr.show_acl.ShowAclEthernetServices'> |
INFO : |====================================================================================================================================================|
INFO : iosxr1: afterAcl get
INFO : +====================================================================================================================================================+
INFO : | Commands for learning feature 'Acl' |
INFO : +====================================================================================================================================================+
INFO : | - Parsed commands |
INFO : |----------------------------------------------------------------------------------------------------------------------------------------------------|
INFO : | cmd: <class 'genie.libs.parser.iosxe.show_acl.ShowAccessLists'> |
INFO : |====================================================================================================================================================|
INFO : csr1000v-1: afterAcl get
INFO : ********************************Config戻し********************************
INFO : iosxr1: clear_config
INFO : csr1000v-1: clear_config
INFO : ********************************ログアウト********************************
INFO : iosxr1: disconnected
INFO : csr1000v-1: disconnected
INFO : ********************************Diff********************************
=====iosxr1_ConfigDiff=====
+Wed Dec 18 16:57:14.949 UTC:
-Wed Dec 18 16:56:58.216 UTC:
router static:
address-family ipv4 unicast:
+ 8.8.8.8/32 10.10.20.254:
=====csr1000v-1_ConfigDiff=====
+Current configuration : 7620 bytes:
+ip route 8.8.8.8 255.255.255.255 GigabitEthernet1 10.10.20.254:
-Current configuration : 7557 bytes:
=====iosxr1_RouteDiff=====
info:
vrf:
default:
address_family:
ipv4:
routes:
+ 8.8.8.8/32:
+ active: True
+ metric: 0
+ next_hop:
+ next_hop_list:
+ 1:
+ index: 1
+ next_hop: 10.10.20.254
+ updated: 00:00:08
+ route: 8.8.8.8/32
+ route_preference: 1
+ source_protocol: static
+ source_protocol_codes: S
=====csr1000v-1_RouteDiff=====
info:
vrf:
default:
address_family:
ipv4:
routes:
+ 8.8.8.8/32:
+ active: True
+ metric: 0
+ next_hop:
+ next_hop_list:
+ 1:
+ index: 1
+ next_hop: 10.10.20.254
+ outgoing_interface: GigabitEthernet1
+ route: 8.8.8.8/32
+ route_preference: 1
+ source_protocol: static
+ source_protocol_codes: S
=====iosxr1_AclDiff=====
=====csr1000v-1_AclDiff=====
INFO : ********************************終了********************************
(nw_automate) 00:28:00 my Script $