LoginSignup
2
4

More than 3 years have passed since last update.

Ansibleのparse_cli_textfsmフィルターを使ったNWテスト ①事前事後の無影響確認

Last updated at Posted at 2019-05-14

はじめに

Ansibleのparse_cli_textfsmフィルター、set_factモジュール、assertモジュールを組み合わせると、CLIコマンド出力結果を元に様々なネットワークテストができます。

以前Cisco IOSで、EIGRPのネイバーが存在するか、UPTIMEが1分より大きいか確認するPlaybookをご紹介しました。
Ansibleのparse_cli_textfsmフィルターを使ったネットワークテストの自動化

上記は想定結果を指定するパターンですが、それ以外にも、事前事後でステータスが変わらないことを確認する場面も良くあるかと思います。
(NW機器のリプレイス、OSアップグレード、その他小規模メンテナンス作業等)

今回はCisco IOSのshow ip routeコマンドで表示される宛先、ネクストホップ、プロトコルが、事前事後で変わらないことを確認するPlaybookを作成してみました。

Inventory

3台のCisco CSR1000Vを用意し、お互いにEIGRPでルーティング情報をやりとりする構成にしています。
確認結果をシンプルにするため、Inventory上は1台のみ指定しています。

inventory
[cisco]
192.168.1.200 ansible_user=csr1 ansible_password=cisco ansible_become_pass=csr1

[cisco:vars]
ansible_network_os=ios
ansible_become=yes
ansible_become_method=enable

Playbook

大まかな流れとしては、
<事前作業>
 (1) show ip routeの取得
 (2) textFSMでパース
 (3) 宛先、マスク、ネクストホップアドレス、プロトコルだけを抽出
 (4) 昇順でソート
 (5) 確認のため、(2)と(4)の結果をdebugで出力
<事後作業>
 (6)~(10) 上記(1)~(5)と同じことを実施
 (11) (5)と(10)の値をassertモジュールで比較
といった感じです。
実際のケースでは、(5)と(6)の間でOSアップグレードなどを行うイメージです。

playbook.yml
---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: run show command on remote devices (before) #(1)
      ios_command:
        commands:
          - show ip route
      register: result_before

    - name: get parsed routing table using textfsm (before) #(2)
      set_fact:
        route_before: "{{ result_before.stdout[0] | parse_cli_textfsm('./ntc-templates-master/templates/cisco_ios_show_ip_route.template') }}"

    - name: extract necessary data from routing table (before) #(3)
      set_fact:
        extracted_route_before: "{{ extracted_route_before + [[item.NETWORK, item.MASK, item.NEXTHOP_IP, item.PROTOCOL]] }}"
      loop: "{{ route_before | flatten(levels=1) }}"

    - name: sort extracted route (before) #(4)
      set_fact:
        sorted_route_before: "{{ extracted_route_before | sort }}"

    - name: debug (before) #(5)
      debug:
        msg:
          - "{{ route_before }}"
          - "{{ sorted_route_before }}"


    - name: run show command on remote devices (after) #(6)
      ios_command:
        commands:
          - show ip route
      register: result_after

    - name: get parsed routing table using textfsm (after) #(7)
      set_fact:
        route_after: "{{ result_after.stdout[0] | parse_cli_textfsm('./ntc-templates-master/templates/cisco_ios_show_ip_route.template') }}"

    - name: extract necessary data from routing table (after) #(8)
      set_fact:
        extracted_route_after: "{{ extracted_route_after + [[item.NETWORK, item.MASK, item.NEXTHOP_IP, item.PROTOCOL]] }}"
      loop: "{{ route_after | flatten(levels=1) }}"

    - name: sort extracted route (after) #(9)
      set_fact:
        sorted_route_after: "{{ extracted_route_after | sort }}"

    - name: debug (after) #(10)
      debug:
        msg:
          - "{{ route_after }}"
          - "{{ sorted_route_after }}"


    - name: assert that before and ater routing tables are the same #(11)
      assert:
        that:
          - "sorted_route_before == sorted_route_after"


  vars:
    extracted_route_before: []
    extracted_route_after: []

出力結果

長いので(6)~(10)は省略しています。
当然と言えば当然なのですが、(11)の比較で差異がないことから、"msg": "All assertions passed"でチェックに合格しています。

[centos@localhost ansible]$ ansible-playbook -i inventory playbook1.yml 

PLAY [cisco] *******************************************************************

TASK [run show command on remote devices (before)] *****************************
ok: [192.168.1.200]

TASK [get parsed routing table using textfsm (before)] *************************
ok: [192.168.1.200]

TASK [extract necessary data from routing table (before)] **********************
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:50', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.1.0.0', 'METRIC': u'130816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.201', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:50', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.1.1.0', 'METRIC': u'130816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.201', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:50', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.1.2.0', 'METRIC': u'130816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.201', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:50', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.1.3.0', 'METRIC': u'130816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.201', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:50', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.1.4.0', 'METRIC': u'130816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.201', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:15', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.2.0.0', 'METRIC': u'2816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.202', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:12', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.2.1.0', 'METRIC': u'2816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.202', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:09', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.2.2.0', 'METRIC': u'2816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.202', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:06', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.2.3.0', 'METRIC': u'2816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.202', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'90', 'UPTIME': u'02:27:03', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'10.2.4.0', 'METRIC': u'2816', 'MASK': u'24', 'NEXTHOP_IP': u'192.168.1.202', 'PROTOCOL': u'D', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'', 'UPTIME': u'', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'192.168.1.0', 'METRIC': u'', 'MASK': u'24', 'NEXTHOP_IP': u'', 'PROTOCOL': u'C', 'TYPE': u''})
ok: [192.168.1.200] => (item={'DISTANCE': u'', 'UPTIME': u'', 'NEXTHOP_IF': u'GigabitEthernet1', 'NETWORK': u'192.168.1.200', 'METRIC': u'', 'MASK': u'32', 'NEXTHOP_IP': u'', 'PROTOCOL': u'L', 'TYPE': u''})

TASK [sort extracted route (before)] *******************************************
ok: [192.168.1.200]

TASK [debug (before)] **********************************************************
ok: [192.168.1.200] => {
    "msg": [
        [
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "130816", 
                "NETWORK": "10.1.0.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.201", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:50"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "130816", 
                "NETWORK": "10.1.1.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.201", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:50"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "130816", 
                "NETWORK": "10.1.2.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.201", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:50"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "130816", 
                "NETWORK": "10.1.3.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.201", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:50"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "130816", 
                "NETWORK": "10.1.4.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.201", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:50"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "2816", 
                "NETWORK": "10.2.0.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.202", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:15"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "2816", 
                "NETWORK": "10.2.1.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.202", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:12"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "2816", 
                "NETWORK": "10.2.2.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.202", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:09"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "2816", 
                "NETWORK": "10.2.3.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.202", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:06"
            }, 
            {
                "DISTANCE": "90", 
                "MASK": "24", 
                "METRIC": "2816", 
                "NETWORK": "10.2.4.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "192.168.1.202", 
                "PROTOCOL": "D", 
                "TYPE": "", 
                "UPTIME": "02:27:03"
            }, 
            {
                "DISTANCE": "", 
                "MASK": "24", 
                "METRIC": "", 
                "NETWORK": "192.168.1.0", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "", 
                "PROTOCOL": "C", 
                "TYPE": "", 
                "UPTIME": ""
            }, 
            {
                "DISTANCE": "", 
                "MASK": "32", 
                "METRIC": "", 
                "NETWORK": "192.168.1.200", 
                "NEXTHOP_IF": "GigabitEthernet1", 
                "NEXTHOP_IP": "", 
                "PROTOCOL": "L", 
                "TYPE": "", 
                "UPTIME": ""
            }
        ], 
        [
            [
                "10.1.0.0", 
                "24", 
                "192.168.1.201", 
                "D"
            ], 
            [
                "10.1.1.0", 
                "24", 
                "192.168.1.201", 
                "D"
            ], 
            [
                "10.1.2.0", 
                "24", 
                "192.168.1.201", 
                "D"
            ], 
            [
                "10.1.3.0", 
                "24", 
                "192.168.1.201", 
                "D"
            ], 
            [
                "10.1.4.0", 
                "24", 
                "192.168.1.201", 
                "D"
            ], 
            [
                "10.2.0.0", 
                "24", 
                "192.168.1.202", 
                "D"
            ], 
            [
                "10.2.1.0", 
                "24", 
                "192.168.1.202", 
                "D"
            ], 
            [
                "10.2.2.0", 
                "24", 
                "192.168.1.202", 
                "D"
            ], 
            [
                "10.2.3.0", 
                "24", 
                "192.168.1.202", 
                "D"
            ], 
            [
                "10.2.4.0", 
                "24", 
                "192.168.1.202", 
                "D"
            ], 
            [
                "192.168.1.0", 
                "24", 
                "", 
                "C"
            ], 
            [
                "192.168.1.200", 
                "32", 
                "", 
                "L"
            ]
        ]
    ]
}

~ 事後確認は省略 ~

TASK [assert that before and ater routing tables are the same] *****************
ok: [192.168.1.200] => {
    "changed": false, 
    "msg": "All assertions passed"
}

PLAY RECAP *********************************************************************
192.168.1.200              : ok=11   changed=0    unreachable=0    failed=0   

[centos@localhost ansible]$ 

終わりに

今回はルーティング確認でしたが、textFSMのテンプレートは他にも様々なものが提供されていますので、応用が利くと思います。
GitHub networktocode/ntc-templates

「ここをこうした方がもっとシンプルだ!」等コメントあれば頂けると嬉しいです。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4