Ansibleのnapalm-ansibleモジュールでネットワークテストを自動化してみた

# はじめに

前回は、Ansibleのサードパーティー製モジュール群napalm-ansibleの内、設定変更系のモジュール`napalm_install_config`の動作確認を行いました。
[Ansibleのnapalm-ansibleモジュールでCisco IOSの設定変更をしてみた](https://qiita.com/tech_kitara/items/8ef2c4181f71e27171a3)

今回は、`napalm_validate`モジュールを使って、ネットワークテストの自動化をしてみました。

※[napalm-ansibleの概要](https://qiita.com/tech_kitara/items/8ef2c4181f71e27171a3#napalm-ansible%E3%81%A8%E3%81%AF)、[セットアップ方法](https://qiita.com/tech_kitara/items/8ef2c4181f71e27171a3#%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)は前回の記事を参照願います。ただし、今回はSCPサーバの有効化は不要です。


# napalm_validateモジュールで出来ること

想定結果を記載したYAML形式のファイル(Validationファイル)と、実機のステータスを比較し、その結果を出力することが可能です。

例えば、以下のようなValidationファイルを作成し、GigabitEthernet1の

- (1)  rxのエラーカウントがゼロであること
- (2)  rxのUnicastパケットカウントが10より大きいこと(カウントアップが見られること)
- (3)  txのエラーカウントがゼロであること
- (4)  txのUnicastパケットカウントが10より大きいこと(カウントアップが見られること)

を想定結果として記載します。

```yaml:validate_sample.yml
---
- get_interfaces_counters:
    GigabitEthernet1:
      rx_errors: 0                #(1)
      rx_unicast_packets: '>10'   #(2)
      tx_errors: 0                #(3)
      tx_unicast_packets: '>10'   #(4)
```

これに対し、取得したインタフェース情報(以下のような`napalm_get_facts` モジュールで取得したデータ)を比較しているイメージです。

```yaml:
"napalm_interfaces_counters": {
                "GigabitEthernet1": {
                    "rx_broadcast_packets": 0, 
                    "rx_discards": 0, 
                    "rx_errors": 0,                 #(1)
                    "rx_multicast_packets": 0, 
                    "rx_octets": 11616019, 
                    "rx_unicast_packets": 97110,    #(2)
                    "tx_broadcast_packets": -1, 
                    "tx_discards": 0, 
                    "tx_errors": 0,                 #(3)
                    "tx_multicast_packets": -1, 
                    "tx_octets": 8023672, 
                    "tx_unicast_packets": 61613     #(4)
                }
```

※`napalm_get_facts` モジュールについては、てくなべ (tekunabe)さんのブログで分かり易くまとめられています。
[Ansible の napalm-ansible モジュール群でCisco IOS 機器の様々な情報を取得する](https://tekunabe.hatenablog.jp/entry/2017/05/27/napalm-ansible_fatcs)

Cisco IOSで取得できる情報は、執筆時点(2018年11月)で以下の通りです。

- get_arp_table
- get_bgp_neighbors
- get_bgp_neighbors_detail
- get_config
- get_environment
- get_facts
- get_interfaces
- get_interfaces_counters
- get_interfaces_ip
- get_ipv6_neighbors_table
- get_lldp_neighbors
- get_lldp_neighbors_detail
- get_mac_address_table
- get_network_instances
- get_ntp_peers
- get_ntp_servers
- get_ntp_stats
- get_optics
- get_probes_config
- get_snmp_information
- get_users
- is_alive
- ping
- traceroute

※最新情報は[こちら](https://napalm.readthedocs.io/en/latest/support/index.html#getters-support-matrix)を参照願います。

# Ansible実行(成功例)

### Validationファイル
数ある中から、個人的に使えそうなものをピックアップしてみました。

```yaml:validate.yml
---
- get_facts:
    hostname: csr1
    os_version: CSR1000V Software, Version 15.5(3)S5, RELEASE SOFTWARE (fc2)
    interface_list:
      _mode: strict
      list:
        - GigabitEthernet1
        - GigabitEthernet2
        - GigabitEthernet3
        - Tunnel1
        - Loopback0

- get_interfaces_ip:
    GigabitEthernet1:
      ipv4:
        192.168.1.100:
          prefix_length: 24

- get_interfaces:
    GigabitEthernet1:
      is_enabled: true
      speed: 1000

- get_interfaces_counters:
    GigabitEthernet1:
      rx_errors: 0
      rx_unicast_packets: '>10'
      tx_errors: 0
      tx_unicast_packets: '>10'

- get_environment:
    memory:
      used_ram: '<400000000'  #メモリ使用域が400,000,000Byteより低いこと
    cpu:
       0:
        '%usage': '<15.0'   #CPU使用率が15%より低いこと

- ping:
    _name: ping_gateway
    _kwargs:
      destination: 192.168.1.1
      source: 192.168.1.100
      size: 1500
      count: 100
    success:
      packet_loss: 0      #パケロスがないこと
      rtt_max: '<1000.0'  #1=0.1msec ⇒ 100msecより速いこと
      rtt_avg: '<500.0'   #1=0.1msec ⇒ 50msecより速いこと
    _mode: strict
```

### Playbook

基本的にはValidationファイルのパスを指定するだけの、シンプルなものになっています。

```yaml:playbook7.yml
---
- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: get validation report
      napalm_validate:
        provider: "{{ cli }}"
        validation_file: '/home/<ユーザ名>/ansible/validate.yml'
      register: result

    - name: debug
      debug:
        msg: "{{ result }}"

  vars:
    cli:
      hostname: "{{ inventory_hostname }}"
      username: "{{ ansible_username }}"
      password: "{{ ansible_password }}"
      dev_os: "ios"
      #ユーザーの権限レベルが0の場合、以下コマンドでenableパスワードを指定
      optional_args: {'secret': 'csr1'}
```

### 実行結果
それぞれの確認項目について、`"complies": true or false`で結果が分かります。
成功した場合、CPU使用率等の具体的な値は表示されませんので、必要に応じて`napalm_get_facts`モジュールで値を確認すると良いと思います。

```yaml:
[<ユーザ名>@localhost library]$ ansible-playbook -i inventory playbook7.yml

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

TASK [get validation report] ****************************************************************
ok: [192.168.1.100]

TASK [debug] ********************************************************************************
ok: [192.168.1.100] => {
    "msg": {
        "changed": false,
        "compliance_report": {
            "complies": true,
            "get_environment": {
                "complies": true,
                "extra": [],
                "missing": [],
                "present": {
                    "cpu": {
                        "complies": true,
                        "nested": true
                    },
                    "memory": {
                        "complies": true,
                        "nested": true
                    }
                }
            },
            "get_facts": {
                "complies": true,
                "extra": [],
                "missing": [],
                "present": {
                    "hostname": {
                        "complies": true,
                        "nested": false
                    },
                    "interface_list": {
                        "complies": true,
                        "nested": true
                    },
                    "os_version": {
                        "complies": true,
                        "nested": false
                    }
                }
            },
            "get_interfaces": {
                "complies": true,
                "extra": [],
                "missing": [],
                "present": {
                    "GigabitEthernet1": {
                        "complies": true,
                        "nested": true
                    }
                }
            },
            "get_interfaces_counters": {
                "complies": true,
                "extra": [],
                "missing": [],
                "present": {
                    "GigabitEthernet1": {
                        "complies": true,
                        "nested": true
                    }
                }
            },
            "get_interfaces_ip": {
                "complies": true,
                "extra": [],
                "missing": [],
                "present": {
                    "GigabitEthernet1": {
                        "complies": true,
                        "nested": true
                    }
                }
            },
            "ping_gateway": {
                "complies": true,
                "extra": [],
                "missing": [],
                "present": {
                    "success": {
                        "complies": true,
                        "nested": true
                    }
                }
            },
            "skipped": []
        },
        "failed": false
    }
}

PLAY RECAP **********************************************************************************
192.168.1.100              : ok=2    changed=0    unreachable=0    failed=0   
```

# Ansible実行(失敗例)

### Validationファイル
Ping確認の想定RTTを一桁減らしてみます。

```yaml:validate2.yml
---
- ping:
    _name: ping_gateway
    _kwargs:
      destination: 192.168.1.1
      source: 192.168.1.100
      size: 1500
      count: 100
    success:
      packet_loss: 0      #パケロスがないこと
      rtt_max: '<100.0'  #1=0.1msec ⇒ 10msecより速いこと
      rtt_avg: '<50.0'   #1=0.1msec ⇒ 5msecより速いこと
    _mode: strict
```

### 実行結果
`-vvvオプション`を使用して、詳細なメッセージを表示した結果です。
`"rtt_avg"`と`"rtt_max"`が想定結果を上回り、`"complies": false`となっているのが分かります。

```yaml:
[<ユーザ名>@localhost library]$ ansible-playbook -i inventory playbook7.yml -vvv
---<中略>---
PLAYBOOK: playbook7.yml *********************************************************************
1 plays in playbook7.yml

PLAY [cisco] ********************************************************************************
META: ran handlers

TASK [get validation report] ****************************************************************
---<中略>---
fatal: [192.168.1.100]: FAILED! => {
    "changed": false,
    "compliance_report": {
        "complies": false,
        "ping_gateway": {
            "complies": false,
            "extra": [],
            "missing": [],
            "present": {
                "success": {
                    "complies": false,
                    "diff": {
                        "complies": false,
                        "extra": [],
                        "missing": [],
                        "present": {
                            "packet_loss": {
                                "complies": true,
                                "nested": false
                            },
                            "rtt_avg": {
                                "actual_value": 130.0,
                                "complies": false,
                                "expected_value": "<50.0",
                                "nested": false
                            },
                            "rtt_max": {
                                "actual_value": 191.0,
                                "complies": false,
                                "expected_value": "<100.0",
                                "nested": false
                            }
                        }
                    },
                    "nested": true
                }
            }
        },
        "skipped": []
    },
    "invocation": {
  ---<中略>--
    "msg": "Device does not comply with policy"
```

# 所感
確認できる項目は限られていますが、一度Validationファイルを作ってしまえば、パラメータ部分を変えて使い回せるので便利だと感じました。
確認できない項目は、ntc-templatesというコマンド出力結果パース用のテンプレート集とassertモジュールを組み合わせて使う選択肢もあると思います。
どちらでもサポートされていない確認項目については、テンプレートを自作するかPythonでコードを書くしかないのかな。。