Genie Parser/OpsによるCisco機器のログ取得・構文解析

# はじめに
Ciscoが公開しているPythonベースのテストフレームワーク/フィーチャーライブラリー「pyATS/Genie」の内、Genie ParserとOpsを使ってみた結果をメモしておきます。

# 1-1. Genie Parserとは
NW機器のshowコマンド結果を構文解析し、テキストデータを「xの値はy」のようにプログラムが扱いやすい形式に変換します。2019年7月時点で1000を超えるパーサーが存在し、Cisco IOS、IOS-XE、IOS-XR、NX-OS、ASA、Junosの各種showコマンドに対応しています。

参考URL:
[Parser一覧](https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/parsers)
[GitHub - genieparser](https://github.com/CiscoTestAutomation/genieparser)

# 1-2. Pythonコード例
Python環境のセットアップは[前回の記事](https://qiita.com/tech_kitara/items/cf39ecf0ae409235bf5d#%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)を参照願います。(Testbedも用意する必要あり。)
以下はCisco CSR1000Vの`show interfaces GigabitEthernet1`をパースする例です。

```python:genie8-1.py
from genie.conf import Genie
from pprint import pprint

testbed = Genie.init('testbed_devnet3.yaml')   # Testbedを指定
device = testbed.devices['csr1000v-1']   # ホスト名を指定

device.connect()

output = device.parse('show interfaces GigabitEthernet1')
pprint(output)
```
# 1-3. 実行結果

```yaml:
$ python genie8-1.py
# 途中の実行ログは省略
{'GigabitEthernet1': {'arp_timeout': '04:00:00',
                      'arp_type': 'arpa',
                      'auto_negotiate': True,
                      'bandwidth': 1000000,
                      'counters': {'in_broadcast_pkts': 0,
                                   'in_crc_errors': 0,
                                   'in_errors': 0,
                                   'in_frame': 0,
                                   'in_giants': 0,
                                   'in_ignored': 0,
                                   'in_mac_pause_frames': 0,
                                   'in_multicast_pkts': 0,
                                   'in_no_buffer': 0,
                                   'in_octets': 3984819,
                                   'in_overrun': 0,
                                   'in_pkts': 41999,
                                   'in_runts': 0,
                                   'in_throttles': 0,
                                   'in_watchdog': 0,
                                   'last_clear': 'never',
                                   'out_babble': 0,
                                   'out_buffer_failure': 0,
                                   'out_buffers_swapped': 0,
                                   'out_collision': 0,
                                   'out_deferred': 0,
                                   'out_errors': 0,
                                   'out_interface_resets': 0,
                                   'out_late_collision': 0,
                                   'out_lost_carrier': 0,
                                   'out_mac_pause_frames': 0,
                                   'out_no_carrier': 0,
                                   'out_octets': 414844,
                                   'out_pkts': 2545,
                                   'out_underruns': 0,
                                   'out_unknown_protocl_drops': 168,
                                   'rate': {'in_rate': 3000,
                                            'in_rate_pkts': 5,
                                            'load_interval': 300,
                                            'out_rate': 0,
                                            'out_rate_pkts': 0}},
                      'delay': 10,
                      'description': "MANAGEMENT INTERFACE - DON'T TOUCH ME",
                      'duplex_mode': 'full',
                      'enabled': True,
                      'encapsulations': {'encapsulation': 'arpa'},
                      'flow_control': {'receive': False, 'send': False},
                      'ipv4': {'10.10.20.48/24': {'ip': '10.10.20.48',
                                                  'prefix_length': '24'}},
                      'keepalive': 10,
                      'last_input': '00:00:00',
                      'last_output': '00:00:00',
                      'line_protocol': 'up',
                      'link_type': 'auto',
                      'mac_address': '0050.56ac.6cb1',
                      'media_type': 'Virtual',
                      'mtu': 1500,
                      'oper_status': 'up',
                      'output_hang': 'never',
                      'phys_address': '0050.56ac.6cb1',
                      'port_channel': {'port_channel_member': False},
                      'port_speed': '1000',
                      'queues': {'input_queue_drops': 0,
                                 'input_queue_flushes': 0,
                                 'input_queue_max': 375,
                                 'input_queue_size': 0,
                                 'output_queue_max': 40,
                                 'output_queue_size': 0,
                                 'queue_strategy': 'fifo',
                                 'total_output_drop': 0},
                      'reliability': '255/255',
                      'rxload': '1/255',
                      'txload': '1/255',
                      'type': 'CSR vNIC'}}
```

# 2-1. Genie Opsとは
デバイスの設定情報やオペレーション状態をフィーチャー単位（1つのshowコマンドではなく、例えばinterfaceに関連する複数のshowコマンド）で取得するPythonオブジェクトです。
以前ご紹介した[Genie Conf](https://qiita.com/tech_kitara/items/cf39ecf0ae409235bf5d)と同様、OSによるコマンドの違いを意識することなく、情報の取得が可能です。例えば、IOS-XEでinterface関連の情報を取得する場合、Genie Opsのlearnメソッドにて、以下のCLIコマンドが実行されます。

- show interfaces
- show vrf
- show interfaces accounting
- show ip interface
- show ipv6 interface

他にも、routing、bgp、ospf、mcast、acl、vlan、stp、platform等のフィーチャーをサポートしています（詳細は下記GitHubを参照）。
出力結果（info）は、Parserでパースしたものが出力されます。

参考URL:
[Ops User Guide](https://pubhub.devnetcloud.com/media/pyats-packages/docs/genie/userguide/Ops/user/index.html)
[DevNet Workshop (DEVWKS-2601)- pyATS Genie Ops and Parsers](https://github.com/RunSi/DEVWKS-2601)
[GitHub - Genie Ops](https://github.com/CiscoTestAutomation/genielibs/tree/master/pkgs/ops-pkg/src/genie/libs/ops)

# 2-2. Pythonコード例①（interface）
interface関連のフィーチャーを取得する例です。learnメソッド内の引数で、`interface`を指定することで、関連情報を取得できます。

```python:genie8-2.py
from genie.conf import Genie
from pprint import pprint

testbed = Genie.init('testbed_devnet3.yaml')
device = testbed.devices['csr1000v-1']

device.connect()

interface = device.learn('interface')
pprint(interface.info)
```

# 2-3. 実行結果①（interface）
Genie Parserの結果と比較すると、accounting関連の情報が追加されています。また、interfaceのステータス情報は、エラーカウント内訳などが表示されずコンパクトになっています。

```yaml:
$ python genie8-2.py
# 途中の実行ログは省略
{'': {'accounting': {'arp': {'chars_in': 480,
                             'chars_out': 540,
                             'pkts_in': 8,
                             'pkts_out': 9},
                     'ip': {'chars_in': 4689480,
                            'chars_out': 566379,
                            'pkts_in': 50575,
                            'pkts_out': 3530},
                     'other': {'chars_in': 32820,
                               'chars_out': 634,
                               'pkts_in': 428,
                               'pkts_out': 10}},
      'switchport_enable': False},
 'GigabitEthernet1': {'auto_negotiate': True,
                      'bandwidth': 1000000,
                      'counters': {'in_broadcast_pkts': 0,
                                   'in_crc_errors': 0,
                                   'in_errors': 0,
                                   'in_mac_pause_frames': 0,
                                   'in_multicast_pkts': 0,
                                   'in_octets': 4873797,
                                   'in_pkts': 51634,
                                   'last_clear': 'never',
                                   'out_errors': 0,
                                   'out_mac_pause_frames': 0,
                                   'out_octets': 561534,
                                   'out_pkts': 3504,
                                   'rate': {'in_rate': 3000,
                                            'in_rate_pkts': 4,
                                            'load_interval': 300,
                                            'out_rate': 0,
                                            'out_rate_pkts': 0}},
                      'delay': 10,
                      'description': "MANAGEMENT INTERFACE - DON'T TOUCH ME",
                      'duplex_mode': 'full',
                      'enabled': True,
                      'encapsulation': {'encapsulation': 'arpa'},
                      'flow_control': {'receive': False, 'send': False},
                      'ipv4': {'10.10.20.48/24': {'ip': '10.10.20.48',
                                                  'prefix_length': '24',
                                                  'secondary': False}},
                      'mac_address': '0050.56ac.6cb1',
                      'mtu': 1500,
                      'oper_status': 'up',
                      'phys_address': '0050.56ac.6cb1',
                      'port_channel': {'port_channel_member': False},
                      'port_speed': '1000',
                      'switchport_enable': False,
                      'type': 'CSR vNIC'},
# GigabitEthernet2,3の結果は省略
                     }
```

#2-4. Pythonコード例②（interface一部表示）
出力結果は辞書形式のため、特定のキーの値のみを表示できます。例えば、コード例①を以下の通り書き換えることで、GigabitEthernet1のオペレーション状態（up/down）のみ表示できます。

```python:
pprint(interface.info['GigabitEthernet1']['oper_status'])
```

#2-5. 実行結果②（interface一部表示）

```
'up'
```

# 2-6. Pythonコード例③（interface一部取得）
コード例②は情報を一式取得した後、表示の段階で絞っていましたが、取得段階で対象を絞ることも可能です。例えば、各IFのBandwidthだけ取得できれば良い場合、learnメソッドの引数に、以下の通り`attributes`を指定します。

```python:
interface = device.learn('interface', attributes=['info[(.*)][bandwidth]'])
```

# 2-7. 実行結果③（interface一部取得）
実行中のログは省略していますが、取得コマンドは`show interfaces`と`show vrf`の2つに絞られていました。実行時間を短くしたい場合は、こちらの方が良いかも知れません。

```yaml:
{'GigabitEthernet1': {'bandwidth': 1000000, 'switchport_enable': False},
 'GigabitEthernet2': {'bandwidth': 1000000, 'switchport_enable': False},
 'GigabitEthernet3': {'bandwidth': 1000000, 'switchport_enable': False}}
```

# 2-8. Pythonコード例④（routing）
learnメソッド内の引数を、`routing`を変更することでrouting関連情報を取得できます。

```python:genie8-4.py
from genie.conf import Genie
from pprint import pprint

testbed = Genie.init('testbed_devnet3.yaml')
device = testbed.devices['csr1000v-1']

device.connect()

routing = device.learn('routing')
pprint(routing.info)
```

# 2-9. 実行結果④（routing）

```yaml:
$ python genie8-4.py
# 途中の実行ログは省略
{'vrf': {'default': {'address_family': {'ipv4': {'routes': {'0.0.0.0/0': {'active': True,
                                                                          'metric': 0,
                                                                          'next_hop': {'next_hop_list': {1: {'index': 1,
                                                                                                             'next_hop': '10.10.20.254',
                                                                                                             'outgoing_interface': 'GigabitEthernet1'}}},
                                                                          'route': '0.0.0.0/0',
                                                                          'route_preference': 1,
                                                                          'source_protocol': 'static',
                                                                          'source_protocol_codes': 'S*'},
                                                            '10.10.20.0/24': {'active': True,
                                                                              'next_hop': {'outgoing_interface': {'GigabitEthernet1': {'outgoing_interface': 'GigabitEthernet1'}}},
                                                                              'route': '10.10.20.0/24',
                                                                              'source_protocol': 'connected',
                                                                              'source_protocol_codes': 'C'},
                                                            '10.10.20.48/32': {'active': True,
                                                                               'next_hop': {'outgoing_interface': {'GigabitEthernet1': {'outgoing_interface': 'GigabitEthernet1'}}},
                                                                               'route': '10.10.20.48/32',
                                                                               'source_protocol': 'local',
                                                                               'source_protocol_codes': 'L'}}}}}}}
```

# 所感
他のパーサーとして、textFSMと[NTC-template](https://github.com/networktocode/ntc-templates)の組み合わせもあります。以下記事にも記載した通り、それぞれ良い所があるので、今後使い分けて行きたいです。

[Ansibleのparse_genieフィルターと通信確認表を使ったNWテスト](https://qiita.com/tech_kitara/items/906c2d87c1e6b470d9e8)