pyATS には Genie Parser がありますが、TextFSM の ntc-templates は昔からあり、知っている人やネットワークの自動化で既に使っている人もいるかと思います
TextFSM は単純にアウトプットを渡すだけなので、pyATS でネットワーク機器へ接続して出力をゲットすれば、簡単に使うことができます。
pyATS と TextFSM をインストール
genie を指定すると pyats と genie の両方インストールされます。
(pyats)$ pip install genie textfsm
ntc-template の取得
show コマンドのアウトプットを TextFSM でパースするために、ntc-template を git で取得します。
(pyats)$ cd $VIRTUAL_ENV
(pyats)$ git clone https://github.com/networktocode/ntc-templates.git
Cloning into 'ntc-templates'...
remote: Enumerating objects: 47, done.
remote: Counting objects: 100% (47/47), done.
remote: Compressing objects: 100% (43/43), done.
remote: Total 6762 (delta 17), reused 24 (delta 4), pack-reused 6715
Receiving objects: 100% (6762/6762), 1.70 MiB | 2.92 MiB/s, done.
Resolving deltas: 100% (3692/3692), done.
pyATS で ntc-template を使う例
それでは genie shell を使って、pyATS でネットワーク機器に接続し TextFSM でパースするまでの例です。
TextFSM のテンプレートを開いて、pyATS で取得した output を渡すというのを行なっています。
$ genie shell --testbed-file tb.yaml
Welcome to Genie Interactive Shell
==================================
Python 3.7.6 (default, Jan 3 2020, 14:59:27)
[Clang 10.0.1 (clang-110.1.46.4)]
>>> from genie.testbed import load
>>> testbed = load('tb.yaml')
-------------------------------------------------------------------------------
>>> import textfsm
>>> template = open('ntc-templates/templates/cisco_ios_show_ip_interface_brief.textfsm', 'r')
>>> with open('ntc-templates/templates/cisco_ios_show_ip_interface_brief.textfsm', 'r') as f:
... template = textfsm.TextFSM(f)
...
>>> dev = testbed.devices['R1_xe']
>>> dev.connect(via='cli')
(snip)
>>> output = dev.execute('show ip interface brief')
[2020-01-11 09:58:16,986] +++ R1_xe: executing command 'show ip interface brief' +++
show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 172.16.1.91 YES TFTP up up
GigabitEthernet2 unassigned YES unset up up
GigabitEthernet2.90 10.12.90.1 YES TFTP up up
GigabitEthernet2.110 10.12.110.1 YES TFTP up up
GigabitEthernet2.115 10.12.115.1 YES TFTP up up
GigabitEthernet2.120 10.12.120.1 YES TFTP up up
GigabitEthernet2.390 10.12.90.1 YES TFTP up up
GigabitEthernet2.410 10.12.110.1 YES TFTP up up
GigabitEthernet2.415 10.12.115.1 YES TFTP up up
GigabitEthernet2.420 10.12.120.1 YES TFTP up up
GigabitEthernet3 unassigned YES unset up up
GigabitEthernet3.90 10.13.90.1 YES TFTP up up
GigabitEthernet3.110 10.13.110.1 YES TFTP up up
GigabitEthernet3.115 10.13.115.1 YES TFTP up up
GigabitEthernet3.120 10.13.120.1 YES TFTP up up
GigabitEthernet3.390 10.13.90.1 YES TFTP up up
GigabitEthernet3.410 10.13.110.1 YES TFTP up up
GigabitEthernet3.415 10.13.115.1 YES TFTP up up
GigabitEthernet3.420 10.13.120.1 YES TFTP up up
GigabitEthernet4 unassigned YES unset up up
GigabitEthernet5 unassigned YES unset up up
GigabitEthernet6 unassigned YES unset up up
GigabitEthernet7 unassigned YES unset up up
Loopback0 10.4.1.1 YES TFTP up up
Loopback300 10.4.1.1 YES TFTP up up
Port-channel12 unassignd YES unset up up
Port-channel13 unassigned YES unset up up
Tunnel0 unassigned YES unset up up
Tunnel1 unassigned YES unset up up
Tunnel2 10.4.1.1 YES unset up up
Tunnel3 10.4.1.1 YES unset up up
Tunnel4 10.4.1.1 YES unset up up
Tunnel5 10.4.1.1 YES unset up up
Tunnel6 10.4.1.1 YES unset up up
Tunnel7 10.4.1.1 YES unset up up
Tunnel8 unassigned YES unset up up
Tunnel9 unassigned YES unset up up
R1_xe#
>>>
>>> parsed = template.ParseText(output)
>>> import json
>>> print(json.dumps(parsed, indent=2, sort_keys=2))
[
[
"GigabitEthernet1",
"172.16.1.91",
"up",
"up"
],
[
"GigabitEthernet2",
"unassigned",
"up",
"up"
],
[
"GigabitEthernet2.90",
"10.12.90.1",
"up",
"up"
],
[
"GigabitEthernet2.110",
"10.12.110.1",
"up",
"up"
],
[
"GigabitEthernet2.115",
"10.12.115.1",
"up",
"up"
],
[
"GigabitEthernet2.120",
"10.12.120.1",
"up",
"up"
],
[
"GigabitEthernet2.390",
"10.12.90.1",
"up",
"up"
],
[
"GigabitEthernet2.410",
"10.12.110.1",
"up",
"up"
],
[
"GigabitEthernet2.415",
"10.12.115.1",
"up",
"up"
],
[
"GigabitEthernet2.420",
"10.12.120.1",
"up",
"up"
],
[
"GigabitEthernet3",
"unassigned",
"up",
"up"
],
[
"GigabitEthernet3.90",
"10.13.90.1",
"up",
"up"
],
[
"GigabitEthernet3.110",
"10.13.110.1",
"up",
"up"
],
[
"GigabitEthernet3.115",
"10.13.115.1",
"up",
"up"
],
[
"GigabitEthernet3.120",
"10.13.120.1",
"up",
"up"
],
[
"GigabitEthernet3.390",
"10.13.90.1",
"up",
"up"
],
[
"GigabitEthernet3.410",
"10.13.110.1",
"up",
"up"
],
[
"GigabitEthernet3.415",
"10.13.115.1",
"up",
"up"
],
[
"GigabitEthernet3.420",
"10.13.120.1",
"up",
"up"
],
[
"GigabitEthernet4",
"unassigned",
"up",
"up"
],
[
"GigabitEthernet5",
"unassigned",
"up",
"up"
],
[
"GigabitEthernet6",
"unassigned",
"up",
"up"
],
[
"GigabitEthernet7",
"unassigned",
"up",
"up"
],
[
"Loopback0",
"10.4.1.1",
"up",
"up"
],
[
"Loopback300",
"10.4.1.1",
"up",
"up"
],
[
"Port-channel12",
"unassigned",
"up",
"up"
],
[
"Port-channel13",
"unassigned",
"up",
"up"
],
[
"Tunnel0",
"unassigned",
"up",
"up"
],
[
"Tunnel1",
"unassigned",
"up",
"up"
],
[
"Tunnel2",
"10.4.1.1",
"up",
"up"
],
[
"Tunnel3",
"10.4.1.1",
"up",
"up"
],
[
"Tunnel4",
"10.4.1.1",
"up",
"up"
],
[
"Tunnel5",
"10.4.1.1",
"up",
"up"
],
[
"Tunnel6",
"10.4.1.1",
"up",
"up"
],
[
"Tunnel7",
"10.4.1.1",
"up",
"up"
],
[
"Tunnel8",
"unassigned",
"up",
"up"
],
[
"Tunnel9",
"unassigned",
"up",
"up"
]
]
>>>
上の結果を見ると全ての項目がパースされてはいませんが、主要な情報はパースされています。また、各インタフェースの情報はリストとして返ってきていることが分かります。
おまけ(genie parser編)
おまけとして genie parser のパース出力例も比較で載せておきます。ntc-templates とは異なり、全ての項目がパースされていることが分かります。
>>> parsed = dev.parse('show ip interface brief')
[2020-01-11 10:24:31,256] +++ R1_xe: executing command 'show ip interface brief' +++
show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 172.16.1.91 YES TFTP up up
GigabitEthernet2 unassigned YES unset up up
GigabitEthernet2.90 10.12.90.1 YES TFTP up up
GigabitEthernet2.110 10.12.110.1 YES TFTP up up
GigabitEthernet2.115 10.12.115.1 YES TFTP up up
GigabitEthernet2.120 10.12.120.1 YES TFTP up up
GigabitEthernet2.390 10.12.90.1 YES TFTP up up
GigabitEthernet2.410 10.12.110.1 YES TFTP up up
GigabitEthernet2.415 10.12.115.1 YES TFTP up up
GigabitEthernet2.420 10.12.120.1 YES TFTP up up
GigabitEthernet3 unassigned YES unset up up
GigabitEthernet3.90 10.13.90.1 YES TFTP up up
GigabitEthernet3.110 10.13.110.1 YES TFTP up up
GigabitEthernet3.115 10.13.115.1 YES TFTP up up
GigabitEthernet3.120 10.13.120.1 YES TFTP up up
GigabitEthernet3.390 10.13.90.1 YES TFTP up up
GigabitEthernet3.410 10.13.110.1 YES TFTP up up
GigabitEthernet3.415 10.13.115.1 YES TFTP up up
GigabitEthernet3.420 10.13.120.1 YES TFTP up up
GigabitEthernet4 unassigned YES unset up up
GigabitEthernet5 unassigned YES unset up up
GigabitEthernet6 unassigned YES unset up up
GigabitEthernet7 unassigned YES unset up up
Loopback0 10.4.1.1 YES TFTP up up
Loopback300 10.4.1.1 YES TFTP up up
Port-channel12 unassigned YES unset up up
Port-channel13 unassigned YES unset up up
Tunnel0 unassigned YES unset up up
Tunnel1 unassigned YES unset up up
Tunnel2 10.4.1.1 YES unset up up
Tunnel3 10.4.1.1 YES unset up up
Tunnel4 10.4.1.1 YES unset up up
Tunnel5 10.4.1.1 YES unset up up
Tunnel6 10.4.1.1 YES unset up up
Tunnel7 10.4.1.1 YES unset up up
Tunnel8 unassigned YES unset up up
Tunnel9 unassigned YES unset up up
R1_xe#
>>> print(json.dumps(parsed, indent=2, sort_keys=True))
{
"interface": {
"GigabitEthernet1": {
"interface_is_ok": "YES",
"ip_address": "172.16.1.91",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.110": {
"interface_is_ok": "YES",
"ip_address": "10.12.110.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.115": {
"interface_is_ok": "YES",
"ip_address": "10.12.115.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.120": {
"interface_is_ok": "YES",
"ip_address": "10.12.120.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.390": {
"interface_is_ok": "YES",
"ip_address": "10.12.90.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.410": {
"interface_is_ok": "YES",
"ip_address": "10.12.110.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.415": {
"interface_is_ok": "YES",
"ip_address": "10.12.115.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.420": {
"interface_is_ok": "YES",
"ip_address": "10.12.120.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet2.90": {
"interface_is_ok": "YES",
"ip_address": "10.12.90.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.110": {
"interface_is_ok": "YES",
"ip_address": "10.13.110.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.115": {
"interface_is_ok": "YES",
"ip_address": "10.13.115.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.120": {
"interface_is_ok": "YES",
"ip_address": "10.13.120.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.390": {
"interface_is_ok": "YES",
"ip_address": "10.13.90.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.410": {
"interface_is_ok": "YES",
"ip_address": "10.13.110.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.415": {
"interface_is_ok": "YES",
"ip_address": "10.13.115.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.420": {
"interface_is_ok": "YES",
"ip_address": "10.13.120.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet3.90": {
"interface_is_ok": "YES",
"ip_address": "10.13.90.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"GigabitEthernet4": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"GigabitEthernet5": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"GigabitEthernet6": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"GigabitEthernet7": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Loopback0": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"Loopback300": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "TFTP",
"protocol": "up",
"status": "up"
},
"Port-channel12": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Port-channel13": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel0": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel1": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel2": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel3": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel4": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel5": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel6": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel7": {
"interface_is_ok": "YES",
"ip_address": "10.4.1.1",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel8": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
},
"Tunnel9": {
"interface_is_ok": "YES",
"ip_address": "unassigned",
"method": "unset",
"protocol": "up",
"status": "up"
}
}
}
>>>
Genie Parser のパースデータでは、interface をキーとして階層化されているので、interface 名を知っている場合には下記のように簡単にアクセスできるというメリットがあります。
>>> output['interface']['GigabitEthernet1']
{'ip_address': '172.16.1.91', 'interface_is_ok': 'YES', 'method': 'TFTP', 'status': 'up', 'protocol': 'up'}
ntc-templates のようにリストになっていると対象のインタフェースを見つけるまでループする必要が生じてしまいます。そのため、Genie Parser では基本的にリストは使わず、ネストされた構造体になっています。