1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ansibleのparse_cli_textfsmフィルターを使ったNWテスト ③通信確認表によるチェック

Last updated at Posted at 2019-05-14

はじめに

前回・前々回に続き、今回は、通信確認表で定義した想定結果と、実際の結果が同じか、Ansibleでチェックする方法をご紹介したいと思います。

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

おそらくテストパターンの中で自動化が一番面倒で、実現方法も色々とあると思います。
今回はAnsible2.8で登場予定のread_csvモジュールを使い、CSV形式の通信確認表をリスト形式に変換し、同じくリスト形式にした実際の結果と比較してみます。

通信確認表

ホスト名csr1、csr2の機器を新規導入する想定で、ルーティングテーブルの確認表を作成しました。
HOSTは確認対象のホスト名、ADD_DELはルート追加・削除、PROTOCOLはEIGRP(D)や直接接続(C)、ローカル(L)、NETWORK/MASKは宛先ネットワーク、NEXTHOP_IPはネクストホップIPアドレス、RESULTは結果を記載する欄になります。

今回は簡単のために、csr1のみチェックしてみます。

route_check_sheet.csv
CSV.png

Inventory

AnsibleをインストールしたLinux上で、csr1をhosts登録しています。

inventory1
[cisco]
csr1 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) CSV形式の通信確認表をリスト形式で出力
 (2) 宛先、マスク、ネクストホップアドレス、プロトコルだけを抽出
 (3) 昇順でソート
 (4) 確認のため、(1)と(3)の結果をdebugで出力
<実際の結果を取得>
 (5) show ip routeの取得
 (6) textFSMでパース
 (7) 宛先、マスク、ネクストホップアドレス、プロトコルだけを抽出
 (8) 昇順でソート
 (9) 確認のため、(6)と(8)の結果をdebugで出力
<通信確認>
 (10) (3)と(8)の値をassertモジュールで比較
といった感じです。

(1)で出力されたリストには、csr1とcsr2両方の確認項目が含まれているため、(2)でloop(繰り返し)とwhen(条件分岐)を組み合わせ、対象となるcsr1の項目のみを抽出しています。
(3)~(10)は前回、前々回のものを流用しています。

playbook3.yml
---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: read routing data from CSV file and return a list #(1)
      read_csv:
        path: route_check_sheet.csv
      register: route_intended

    - name: extract necessary data from routing table (intended) #(2)
      set_fact:
        extracted_route_intended: "{{ extracted_route_intended + [[item.NETWORK, item.MASK, item.NEXTHOP_IP, item.PROTOCOL]] }}"
      when: item.HOST == "{{ inventory_hostname }}"
      loop: "{{ route_intended.list | flatten(levels=1) }}"

    - name: sort extracted route (intended) #(3)
      set_fact:
        sorted_route_intended: "{{ extracted_route_intended | sort }}"

    - name: debug (intended) #(4)
      debug:
        msg:
          - "{{ route_intended }}"
          - "{{ sorted_route_intended }}"


    - name: run show command on remote devices (actual) #(5)
      ios_command:
        commands:
          - show ip route
      register: result_actual

    - name: get parsed routing table using textfsm (actual) #(6)
      set_fact:
        route_actual: "{{ result_actual.stdout[0] | parse_cli_textfsm('./ntc-templates-master/templates/cisco_ios_show_ip_route.template') }}"

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

    - name: sort extracted route (actual) #(8)
      set_fact:
        sorted_route_actual: "{{ extracted_route_actual | sort }}"

    - name: debug (actual) #(9)
      debug:
        msg:
          - "{{ route_actual }}"
          - "{{ sorted_route_actual }}"


    - name: assert that intended and actual routing tables are the same #(9)
      assert:
        that:
          - "sorted_route_intended == sorted_route_actual"


  vars:
    extracted_route_intended: []
    extracted_route_actual: []

出力結果

(2)で、HOSTがcsr1でないものは、処理がskippingされていることが分かります。
また最後の(10)で、問題なく"msg": "All assertions passed"となっています。

[centos@localhost ansible]$ ansible-playbook -i inventory1 playbook3.yml

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

TASK [read routing data from CSV file and return a list] ***********************
ok: [csr1]

TASK [extract necessary data from routing table (intended)] ********************
 [WARNING]: when statements should not include jinja2 templating delimiters
such as {{ }} or {% %}. Found: item.HOST == "{{ inventory_hostname }}"

ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.1.0.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.201', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.1.1.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.201', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.1.2.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.201', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.1.3.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.201', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.1.4.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.201', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.2.0.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.2.1.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.2.2.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.2.3.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'D', u'NETWORK': u'10.2.4.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'C', u'NETWORK': u'192.168.1.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'', u'RESULT': u''})
ok: [csr1] => (item={u'HOST': u'csr1', u'PROTOCOL': u'L', u'NETWORK': u'192.168.1.200', u'ADD_DEL': u'+', u'MASK': u'32', u'NEXTHOP_IP': u'', u'RESULT': u''})
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'C', u'NETWORK': u'10.1.0.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'L', u'NETWORK': u'10.1.0.1', u'ADD_DEL': u'+', u'MASK': u'32', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'C', u'NETWORK': u'10.1.1.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'L', u'NETWORK': u'10.1.1.1', u'ADD_DEL': u'+', u'MASK': u'32', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'C', u'NETWORK': u'10.1.2.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'L', u'NETWORK': u'10.1.2.1', u'ADD_DEL': u'+', u'MASK': u'32', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'C', u'NETWORK': u'10.1.3.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'L', u'NETWORK': u'10.1.3.1', u'ADD_DEL': u'+', u'MASK': u'32', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'C', u'NETWORK': u'10.1.4.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'L', u'NETWORK': u'10.1.4.1', u'ADD_DEL': u'+', u'MASK': u'32', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'D', u'NETWORK': u'10.2.0.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'D', u'NETWORK': u'10.2.1.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'D', u'NETWORK': u'10.2.2.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'D', u'NETWORK': u'10.2.3.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'D', u'NETWORK': u'10.2.4.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'192.168.1.202', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'C', u'NETWORK': u'192.168.1.0', u'ADD_DEL': u'+', u'MASK': u'24', u'NEXTHOP_IP': u'', u'RESULT': u''}) 
skipping: [csr1] => (item={u'HOST': u'csr2', u'PROTOCOL': u'L', u'NETWORK': u'192.168.1.201', u'ADD_DEL': u'+', u'MASK': u'32', u'NEXTHOP_IP': u'', u'RESULT': u''}) 

TASK [sort extracted route (intended)] *****************************************
ok: [csr1]

TASK [debug (intended)] ********************************************************
ok: [csr1] => {
    "msg": [
        {
            "changed": false, 
            "dict": {}, 
            "failed": false, 
            "list": [
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.1.0.0", 
                    "NEXTHOP_IP": "192.168.1.201", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.1.1.0", 
                    "NEXTHOP_IP": "192.168.1.201", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.1.2.0", 
                    "NEXTHOP_IP": "192.168.1.201", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.1.3.0", 
                    "NEXTHOP_IP": "192.168.1.201", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.1.4.0", 
                    "NEXTHOP_IP": "192.168.1.201", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.2.0.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.2.1.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.2.2.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.2.3.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "10.2.4.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "24", 
                    "NETWORK": "192.168.1.0", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "C", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr1", 
                    "MASK": "32", 
                    "NETWORK": "192.168.1.200", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "L", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.1.0.0", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "C", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "32", 
                    "NETWORK": "10.1.0.1", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "L", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.1.1.0", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "C", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "32", 
                    "NETWORK": "10.1.1.1", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "L", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.1.2.0", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "C", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "32", 
                    "NETWORK": "10.1.2.1", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "L", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.1.3.0", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "C", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "32", 
                    "NETWORK": "10.1.3.1", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "L", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.1.4.0", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "C", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "32", 
                    "NETWORK": "10.1.4.1", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "L", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.2.0.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.2.1.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.2.2.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.2.3.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "10.2.4.0", 
                    "NEXTHOP_IP": "192.168.1.202", 
                    "PROTOCOL": "D", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "24", 
                    "NETWORK": "192.168.1.0", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "C", 
                    "RESULT": ""
                }, 
                {
                    "ADD_DEL": "+", 
                    "HOST": "csr2", 
                    "MASK": "32", 
                    "NETWORK": "192.168.1.201", 
                    "NEXTHOP_IP": "", 
                    "PROTOCOL": "L", 
                    "RESULT": ""
                }
            ]
        }, 
        [
            [
                "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"
            ]
        ]
    ]
}

# (autualは前回、前回と同様のため省略)

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

PLAY RECAP *********************************************************************
csr1                       : ok=10   changed=0    unreachable=0    failed=0   

[centos@localhost ansible]$ 

最後に

他にも、サードパーティ製のnapalm-ansibleを用いて、YAML形式で想定結果を記載する方法がありますが、read_csvモジュールとparse_cli_textfsmフィルターを使うことで、通信確認がかなり楽になると思いました。
ただし、現状のAnsible2.7台でread_csvモジュールは使えませんので、最新のdevel版をGitHubからインストールするか、お薦めはしませんが、read_csvモジュールのファイル(read_csv.py)のみをお使いのバージョンのlibraryディレクトリに格納して呼び出す必要があります。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?