はじめに
Ansibleのparse_cli_textfsm
フィルター、set_fact
モジュール、assert
モジュールを組み合わせると、CLIコマンド出力結果を元に様々なネットワークテストができます。
今回は、Cisco IOS RouterのEIGRPネイバーテストをAnsibleで自動化してみました。
概要
parse_cli_textfsmフィルターとは
- AnsibleのネットワークCLIフィルタープラグインの1つ。
- TextFSMライブラリを使ってCLIコマンド出力結果をパースする。
- 既存のTextFSMライブラリとして、networktocodeのGitHubリポジトリで公開されているNTC-Templatesがあります。Cisco IOSの場合、サポートしているshowコマンドは2018年11月時点で53種類。(ちなみにshow ip eigrp neighborsは私がプルリクエストを出したものです。)
- インストール方法は、てくなべ (tekunabe)さんのブログが参考になると思います。
set_factモジュールとは
- 変数を格納するためのモジュール。詳細は公式ドキュメントを参照願います。
assertモジュールとは
- True/False判定のためのモジュール。例えば、ある2つの変数が同じ値か、部分集合の関係になっているか、特定の値以上かを判定可能。詳細は公式ドキュメントを参照願います。
ラボ環境
以下の仮想ルータ(Cisco CSR1000V)を同一セグメントに接続し、互いにEIGRPネイバーを形成するようにしました。
機種 | IPアドレス | 備考 |
---|---|---|
test | 192.168.100.200 | |
test2 | 192.168.100.201 | |
csr1 | 192.168.100.202 | Ansibleによるテスト対象 |
showコマンド出力結果は以下の通りです。
csr1#show ip eigrp neighbors
EIGRP-IPv4 Neighbors for AS(10)
H Address Interface Hold Uptime SRTT RTO Q Seq
(sec) (ms) Cnt Num
1 192.168.100.201 Gi1 12 01:07:01 6 100 0 19
0 192.168.100.200 Gi1 12 01:07:01 32 192 0 20
Playbook
例として、EIGRPの以下2点を確認するPlaybookを作成しました。
①想定のEIGRPネイバーが存在するか
②全ネイバーのUPTIMEが1分より大きいか
※変数はPlaybook内に記載しましたが、メンテナンスの観点からInventoryやVariableファイルに書いた方がいいと思います。
---
- hosts: cisco
gather_facts: no
connection: local
tasks:
- name: run show command on remote devices
ios_command:
commands:
- show ip eigrp neighbors # show ip eigrp neighborコマンドを出力。
provider: "{{ cli }}"
register: result
- name: set the actual eigrp neighbor value (all)
set_fact:
actual_eigrp_nei: "{{ result.stdout[0] | parse_cli_textfsm('./ntc-templates-master/templates/cisco_ios_show_ip_eigrp_neighbors.template') }}"
# show ip eigrp neighbors用のTemplateファイルを使って出力結果をパースし、
# リスト/辞書データとしてactual_eigrp_neiに格納。
- name: set the actual eigrp neighbor value (address)
set_fact:
actual_eigrp_nei_address: "{{ actual_eigrp_nei_address + [item.ADDRESS] }}"
loop: "{{ actual_eigrp_nei|flatten(levels=1) }}"
# loopでactual_eigrp_nei内のリストデータを1つずつ読み込み、key=ADDRESSに格納されている
# IPアドレスを、actual_eigrp_nei_addressにAppend。
- name: debug
debug:
msg:
- "{{ actual_eigrp_nei }}" # actual_eigrp_neiに格納されているデータを確認
- "{{ actual_eigrp_nei_address }}" # actual_eigrp_nei_addressに格納されているデータを確認
- name: assert that expected neighbors are in actual neighbors
assert:
that:
- "item in actual_eigrp_nei_address"
loop: "{{ expected_eigrp_nei_address|flatten(levels=1) }}"
# loopでexpected_eigrp_nei_addressのリストを1つずつ読み込み、その値がactual_eigrp_nei_addressに含まれるか確認。
- name: assert that all neighbors are up for more than 1 minute
assert:
that:
- "expected_eigrp_nei_uptime < item.UPTIME"
loop: "{{ actual_eigrp_nei|flatten(levels=1) }}"
# loopでactual_eigrp_neiのリストを1つずつ読み込み、key=UPTIMEの値が想定時間(1分)より大きいか確認。
vars:
cli:
host: "{{ inventory_hostname }}"
username: "{{ ansible_username }}"
password: "{{ ansible_password }}"
authorize: true
auth_pass: "{{ enable_secret }}"
expected_eigrp_nei_address: ["192.168.100.200", "192.168.100.201"] # 想定のEIGRPネイバーアドレス
# リスト形式の空データを作成。loop処理でリスト内にデータをAppendするための元データとして必要。
actual_eigrp_nei_address: []
expected_eigrp_nei_uptime: 00:01:00 # 想定のEIGRP Uptime
#実行結果(成功例)
①②ともに成功し、いずれも"All assertions passed"となっています。
[<ユーザ名>@localhost ansible]$ ansible-playbook -i inventory playbook2_eigrp.yml
PLAY [cisco] ********************************************************************************
TASK [run show command on remote devices] ***************************************************
ok: [192.168.100.202]
TASK [set the actual eigrp neighbor value (all)] ********************************************
ok: [192.168.100.202]
TASK [set the actual eigrp neighbor value (address)] ****************************************
ok: [192.168.100.202] => (item={'Q_CNT': u'0', 'UPTIME': u'01:43:53', 'SEQ_NUM': u'19', 'RTO': u'100', 'SRTT': u'6', 'AS': u'10', 'ADDRESS': u'192.168.100.201', 'INTERFACE': u'Gi1', 'HOLD': u'13'})
ok: [192.168.100.202] => (item={'Q_CNT': u'0', 'UPTIME': u'01:43:53', 'SEQ_NUM': u'20', 'RTO': u'192', 'SRTT': u'32', 'AS': u'10', 'ADDRESS': u'192.168.100.200', 'INTERFACE': u'Gi1', 'HOLD': u'12'})
TASK [debug] ********************************************************************************
ok: [192.168.100.202] => {
"msg": [
[
{
"ADDRESS": "192.168.100.201",
"AS": "10",
"HOLD": "13",
"INTERFACE": "Gi1",
"Q_CNT": "0",
"RTO": "100",
"SEQ_NUM": "19",
"SRTT": "6",
"UPTIME": "01:43:53"
},
{
"ADDRESS": "192.168.100.200",
"AS": "10",
"HOLD": "12",
"INTERFACE": "Gi1",
"Q_CNT": "0",
"RTO": "192",
"SEQ_NUM": "20",
"SRTT": "32",
"UPTIME": "01:43:53"
}
],
[
"192.168.100.201",
"192.168.100.200"
]
]
}
TASK [assert that expected neighbors are in actual neighbors] *******************************
ok: [192.168.100.202] => (item=192.168.100.200) => {
"changed": false,
"item": "192.168.100.200",
"msg": "All assertions passed" # 想定の"192.168.100.200"が実際の出力結果に含まれており、"All assertions passed"。
}
ok: [192.168.100.202] => (item=192.168.100.201) => {
"changed": false,
"item": "192.168.100.201",
"msg": "All assertions passed" # 想定の"192.168.100.201"が実際の出力結果に含まれており、"All assertions passed"。
}
TASK [assert that all neighbors are up for more than 1 minute] ******************************
ok: [192.168.100.202] => (item={'Q_CNT': u'0', 'UPTIME': u'01:43:53', 'SEQ_NUM': u'19', 'RTO': u'100', 'SRTT': u'6', 'AS': u'10', 'ADDRESS': u'192.168.100.201', 'INTERFACE': u'Gi1', 'HOLD': u'13'}) => {
"changed": false,
"item": {
"ADDRESS": "192.168.100.201",
"AS": "10",
"HOLD": "13",
"INTERFACE": "Gi1",
"Q_CNT": "0",
"RTO": "100",
"SEQ_NUM": "19",
"SRTT": "6",
"UPTIME": "01:43:53"
},
"msg": "All assertions passed" # "192.168.100.201"のUPTIMEは1分以上であり、"All assertions passed"。
}
ok: [192.168.100.202] => (item={'Q_CNT': u'0', 'UPTIME': u'01:43:53', 'SEQ_NUM': u'20', 'RTO': u'192', 'SRTT': u'32', 'AS': u'10', 'ADDRESS': u'192.168.100.200', 'INTERFACE': u'Gi1', 'HOLD': u'12'}) => {
"changed": false,
"item": {
"ADDRESS": "192.168.100.200",
"AS": "10",
"HOLD": "12",
"INTERFACE": "Gi1",
"Q_CNT": "0",
"RTO": "192",
"SEQ_NUM": "20",
"SRTT": "32",
"UPTIME": "01:43:53"
},
"msg": "All assertions passed" # "192.168.100.200"のUPTIMEは1分以上であり、"All assertions passed"。
}
PLAY RECAP **********************************************************************************
192.168.100.202 : ok=6 changed=0 unreachable=0 failed=0
#実行結果(失敗例)
EIGRPネイバーをリセットし、UPTIME1分前後の結果を確認しました。
期待通り、UPTIMEが1分ちょうどの時は"Assertion failed"、1分を超えた時は"All assertions passed"となっていることが分かります。
[<ユーザ名>@localhost ansible]$ ansible-playbook -i inventory playbook2_eigrp.yml -vvv
---<中略>---
TASK [assert that all neighbors are up for more than 1 minute] ******************************
task path: /home/<ユーザ名>/ansible/playbook2_eigrp.yml:58
failed: [192.168.100.202] (item={'Q_CNT': u'0', 'UPTIME': u'00:01:00', 'SEQ_NUM': u'19', 'RTO': u'100', 'SRTT': u'6', 'AS': u'10', 'ADDRESS': u'192.168.100.201', 'INTERFACE': u'Gi1', 'HOLD': u'14'}) => {
"assertion": "expected_eigrp_nei_uptime < item.UPTIME",
"changed": false,
"evaluated_to": false,
"item": {
"ADDRESS": "192.168.100.201",
"AS": "10",
"HOLD": "14",
"INTERFACE": "Gi1",
"Q_CNT": "0",
"RTO": "100",
"SEQ_NUM": "19",
"SRTT": "6",
"UPTIME": "00:01:00"
},
"msg": "Assertion failed" # UPTIMEは1分で、想定の"1分より大きい"を満たしておらず"Assertion failed"。
}
ok: [192.168.100.202] => (item={'Q_CNT': u'0', 'UPTIME': u'00:01:01', 'SEQ_NUM': u'20', 'RTO': u'192', 'SRTT': u'32', 'AS': u'10', 'ADDRESS': u'192.168.100.200', 'INTERFACE': u'Gi1', 'HOLD': u'14'}) => {
"changed": false,
"item": {
"ADDRESS": "192.168.100.200",
"AS": "10",
"HOLD": "14",
"INTERFACE": "Gi1",
"Q_CNT": "0",
"RTO": "192",
"SEQ_NUM": "20",
"SRTT": "32",
"UPTIME": "00:01:01"
},
"msg": "All assertions passed" # UPTIMEは1分1秒で、想定結果を満たしているため"All assertions passed"。
}
to retry, use: --limit @/home/<ユーザ名>/ansible/playbook2_eigrp.retry
PLAY RECAP **********************************************************************************
192.168.100.202 : ok=5 changed=0 unreachable=0 failed=1