Ansible自作モジュールでNW機器からTracerouteする

# はじめに

みなさんAnsibleのネットワークモジュール使ってますか？
ネットワークの通信テストを自動化するにあたって、pingだけじゃなくて、tracerouteも出来たらいいなーって思うことありますよね？
そんなアナタのために、Cisco IOS用のios_tracerouteモジュールを作っちゃいましたよ！（無理矢理）



# 使い方
公式のレポジトリにはマージされていないので、使うには以下の操作が必要です。
（Red Hatさん、プルリク出したから通してね:kissing_heart:）

1. GitHubからのデータ取得
GitHubレポジトリ[ansible_ios_traceroute_module](https://github.com/Tachashi/ansible_ios_traceroute_module)をクローンもしくはダウンロード。
  
2. モジュールの格納
自作モジュールをAnsibleが認識できるよう、[Best Practices > Directory Layout]
(https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#directory-layout)に従って`library`ディレクトリを作成し、直下に取得したPythonモジュール（ios_traceroute.py）を格納。

3. パスの追加
`ansible.cfg`ファイルに`library`ディレクトリへのパスを追加。

```ini:ansible.cfg
[defaults]
library = /home/centos/ansible/library
```

※`ansible.cfg`の格納先は、`ansible --version`コマンドの`config file`で確認可能です。

```
[centos@localhost ~]$ ansible --version
ansible 2.9.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/centos/ansible/library']
  ansible python module location = /usr/lib/python3.6/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.6.7 (default, Dec  5 2018, 15:02:05) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
```


# Playbook

## Playbookサンプル
大まかな流れは以下の通りです。

1. `ios_traceroute`モジュールで、NW機器自身のアドレス（192.168.100.201）から172.16.1.100宛てにtracerouteを実施。
2. `debug`モジュールで、出力結果を表示。
3. `assert`モジュールで、7ホップ目に特定のIPアドレス（172.30.100.1）を経由するかチェック。

```yaml:playbook1_traceroute.yml
---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: execute traceroute
      ios_traceroute:
        dest: 172.16.1.100
        probe: 4
        source: 192.168.100.201
        ttl_min: 1
        ttl_max: 20
      register: result_traceroute

    - name: debug traceroute
      debug: 
        msg: "{{ result_traceroute }}"
        
    - name: check if the traceroute results always include specific transit ip address in hop 7
      assert: 
        that:
            - result_traceroute.hop['7'] == ['172.30.100.1']
```

## 使用可能なオプション
`ios_traceroute`モジュール内で使用可能なオプションは以下の通りです。
これらの内、`dest`のみ必須項目になります。それ以外の項目は、指定しない場合、各IOS機器のデフォルト値が使用されます。

```yaml:
  probe:
    description:
    - 各ホップで送信するパケットの数
    type: int
  dest:
    description:
    - 宛先IPアドレスもしくはホスト名（NW機器で名前解決できる必要あり）
    type: str
    required: true
  source:
    description:
    - 送信元IPアドレス
    type: str
  udp:
    description:
    - UDPパケットのポート番号
    type: int
  ttl_min:
    description:
    - 最小TTL値（ttl_minとttl_maxはセットで指定する必要あり）
    type: int
  ttl_max:
    description:
    - 最大TTL値
    type: int
  vrf:
    description:
    - 転送に使用するVRF
    type: str
```


# 実行結果
※グローバルIPアドレスを含んでいるため、[Netconan](https://github.com/intentionet/netconan)というツールを使ってデタラメなアドレスに変換しています。

TASK [debug traceroute]の`commands`で実際に実行したコマンド、`output`で加工前の出力結果、`hop`でホップ数とIPアドレスのみ抽出した結果を確認できます。
最後のTASKで、7ホップ目で想定IPアドレスを経由することが確認でき、「All assertions passed」となっています。

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

TASK [execute traceroute] ***********************************************************************************************
ok: [test3]

TASK [debug traceroute] *************************************************************************************************
ok: [test3] => {
    "msg": {
        "changed": false,
        "commands": [
            "traceroute 172.174.88.165 source 192.168.173.198 probe 4 ttl 1 20"
        ],
        "failed": false,
        "hop": {
            "1": [
                "192.168.173.43"
            ],
            "10": [
                "108.224.66.109",
                "108.224.66.161",
                "108.224.66.172"
            ],
            "11": [
                "108.254.165.36",
                "172.159.216.191"
            ],
            "12": [
                "215.123.152.64",
                "74.246.135.251",
                "222.152.243.188"
            ],
            "13": [
                "108.224.67.238",
                "108.224.67.103"
            ],
            "14": [
                "108.224.80.18",
                "108.224.80.23"
            ],
            "15": [
                "172.174.88.165"
            ],
            "2": [],
            "3": [
                "172.30.231.192"
            ],
            "4": [
                "126.244.74.242"
            ],
            "5": [
                "18.232.93.6",
                "18.232.93.1"
            ],
            "6": [
                "18.235.248.200",
                "18.235.248.207"
            ],
            "7": [
                "68.32.20.116"
            ],
            "8": [],
            "9": [
                "74.246.135.176",
                "108.224.82.72",
                "108.224.82.242"
            ]
        },
        "output": [
            "Type escape sequence to abort.",
            "Tracing the route to 172.174.88.165",
            "VRF info: (vrf in name/id, vrf out name/id)",
            "  1 192.168.173.43 186 msec 16 msec 13 msec 11 msec",
            "  2  *  *  *  * ",
            "  3 172.30.231.192 51 msec 55 msec 44 msec 62 msec",
            "  4 126.244.74.242 54 msec 79 msec 39 msec 59 msec",
            "  5 18.232.93.6 43 msec",
            "    18.232.93.1 63 msec",
            "    18.232.93.6 60 msec",
            "    18.232.93.1 64 msec",
            "  6 18.235.248.200 56 msec 80 msec 53 msec",
            "    18.235.248.207 62 msec",
            "  7 68.32.20.116 61 msec 55 msec 59 msec 39 msec",
            "  8  *  *  *  * ",
            "  9 108.224.82.72 57 msec",
            "    74.246.135.176 39 msec",
            "    108.224.82.242 60 msec 61 msec",
            " 10 108.224.66.161 62 msec",
            "    108.224.66.109 70 msec",
            "    108.224.66.172 72 msec",
            "    108.224.66.161 54 msec",
            " 11 108.254.165.36 [MPLS: Label 27008 Exp 4] 70 msec",
            "    172.159.216.191 [MPLS: Label 178553 Exp 4] 57 msec",
            "    108.254.165.36 [MPLS: Label 25624 Exp 4] 71 msec 56 msec",
            " 12 74.246.135.251 79 msec",
            "    222.152.243.188 77 msec",
            "    74.246.135.251 60 msec",
            "    215.123.152.64 53 msec",
            " 13 108.224.67.238 58 msec 62 msec",
            "    108.224.67.103 70 msec 68 msec",
            " 14 108.224.80.23 58 msec",
            "    108.224.80.18 59 msec",
            "    108.224.80.23 70 msec 70 msec",
            " 15 172.174.88.165 77 msec 60 msec 61 msec 63 msec"
        ]
    }
}

TASK [check if the traceroute results (dict) always include specific transit ip address in hop 7] ***********************
ok: [test3] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP **************************************************************************************************************
test3                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
```

# 最後に
このモジュールは、標準のtracerouteコマンドを使って、1行の中でオプションを指定するようにしています。
複数のルータ、スイッチでテストをしていますが、古いバージョンではコマンドがサポートされておらず、はじかれるかもしれません。その時は諦めてくださいw
プルリクが通ったら、調子に乗って他のOSのモジュールも作ってみようと思います！